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():
parser = argparse.ArgumentParser("Graphs convergence graphs for a collection of simulations.")
parser.add_argument('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")
parser = argparse.ArgumentParser(
"Graphs convergence graphs for a collection of simulations."
)
parser.add_argument(
"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()
data = {}
for file in Path(args.sims_path).iterdir():
sim, frames = Simulation.load(file / "data.squish")
sim, frames = Simulation.load(file)
step = sim.step_size
data[step] = {"times": [], "values": [], "diffs": []}
for i, frame_info in enumerate(frames):
data[step]["times"].append(step * i)
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))
plt.subplots_adjust(.07, .12, .97, .9)
plt.subplots_adjust(0.07, 0.12, 0.97, 0.9)
for step, d in data.items():
ax[0].plot(d["times"], d["values"], label=step)
@ -50,7 +63,7 @@ def main():
print(f"Wrote to {OUTPUT_DIR / 'Equilibrium Convergence.png'}")
if __name__ == '__main__':
if __name__ == "__main__":
os.environ["QT_LOGGING_RULES"] = "*=false"
try:
main()

View File

@ -10,29 +10,48 @@ from squish.common import OUTPUT_DIR
def main():
parser = argparse.ArgumentParser("Graphs average defects at N.")
parser.add_argument('sims_path', metavar='path/to/data',
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")
parser.add_argument(
"sims_path",
metavar="path/to/data",
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()
data = {}
for file in Path(args.sims_path).iterdir():
sim, frames = Simulation.load(file / "data.squish")
files = list(Path(args.sims_path).iterdir())
for i, file in enumerate(files):
sim, frames = Simulation.load(file)
avg_defects = 0
count = 0
for frame in frames:
if np.var(frame["stats"]["avg_radius"]) > 1e-8:
avg_defects += np.count_nonzero(frame["stats"]["site_edge_count"] != 6)
count += 1
avg_defects /= (1 if count == 0 else count)
avg_defects /= 1 if count == 0 else count
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())
ns, defects = np.array([x[0] for x in data]), np.array([x[1] for x in data])
@ -44,9 +63,9 @@ def main():
corrected.append(x)
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)
@ -57,30 +76,31 @@ def main():
ax[0].set_xlabel("N")
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)
x2, y2 = x[14:], np.log10(defects[14:])
x2, y2 = x[40:], np.log(defects[40:])
m2, b2 = np.polyfit(x2, y2, 1)
ax[1].plot(x, y, linestyle='dotted', color='C0')
ax[1].plot(x, np.log10(defects))
ax[1].plot(x, y, linestyle="dotted", color="C0")
ax[1].plot(x, np.log(defects))
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(x, m*x+b, label=f"All N: {m:.5f}")
ax[1].plot(x2, m2 * x2 + b2, label=f"N $\\geq$ 40: {m2:.5f}")
ax[1].grid(zorder=0)
ax[1].legend()
ax[1].set_xlabel("log10 N")
ax[1].set_ylabel("log10 Average Defects")
ax[1].set_xticks(np.arange(3.75, 6.25, 0.25))
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")
print(f"Wrote to {OUTPUT_DIR / 'DefectsN.png'}")
if __name__ == '__main__':
if __name__ == "__main__":
os.environ["QT_log10GING_RULES"] = "*=false"
try:
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]]:
sim, frames = Simulation.load(file / 'data.squish')
sim, frames = Simulation.load(file)
alls = []
for frame_info in frames:
alls.append([
alls.append(
[
frame_info["energy"],
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)
counts = sim.get_distinct()
distincts = []
for j, frame_info in enumerate(sim.frames):
distincts.append([
distincts.append(
[
frame_info["energy"],
np.var(frame_info["stats"]["avg_radius"]) <= 1e-8,
np.count_nonzero(frame_info["stats"]["site_edge_count"] != 6),
counts[j]
])
counts[j],
]
)
return sim.domain.w, alls, distincts
@ -39,7 +43,7 @@ def eq_file_process(file: Path) -> Tuple[float, List[float], List[float]]:
def get_equilibria_data(filepath: Path) -> Tuple[Dict, numpy.ndarray, DomainParams]:
data = {"all": {}, "distinct": {}}
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:
for i, res in enumerate(pool.imap_unordered(eq_file_process, files)):
@ -47,8 +51,12 @@ def get_equilibria_data(filepath: Path) -> Tuple[Dict, numpy.ndarray, DomainPara
data["distinct"][res[0]] = res[2]
hashes = int(21 * i / len(files))
print(f'Loading simulations for N={sim.domain.n}... |{"#"*hashes}{" "*(20-hashes)}|' + \
f' {i+1}/{len(files)} simulations loaded.', flush=True, end='\r')
print(
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)
widths = np.asarray(sorted(data["all"]))
@ -59,10 +67,19 @@ def get_equilibria_data(filepath: Path) -> Tuple[Dict, numpy.ndarray, DomainPara
def main():
# Loading arguments.
parser = argparse.ArgumentParser("Outputs width search data into diagrams")
parser.add_argument('sims_path', metavar='path/to/data',
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")
parser.add_argument(
"sims_path",
metavar="path/to/data",
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()
@ -78,7 +95,9 @@ def main():
disorder_count = []
for width in widths:
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
@ -95,13 +114,19 @@ def main():
fig, ax = plt.subplots(figsize=(12, 8))
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.set_xticks([round(w, 2) for w in widths[::-2]])
ax.set_xticklabels(ax.get_xticks(), rotation=90)
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.set_xlabel("Width")
@ -111,7 +136,7 @@ def main():
print(f"Wrote to {OUTPUT_DIR / filepath}.")
if __name__ == '__main__':
if __name__ == "__main__":
os.environ["QT_LOGGING_RULES"] = "*=false"
try:
main()

View File

@ -9,13 +9,27 @@ from squish.common import OUTPUT_DIR
def main():
parser = argparse.ArgumentParser("Graphs perturbation graphs for a collection of simulations.")
parser.add_argument('sims_path', metavar='path/to/data',
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")
parser = argparse.ArgumentParser(
"Graphs perturbation graphs for a collection of simulations."
)
parser.add_argument(
"sims_path",
metavar="path/to/data",
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()
@ -23,10 +37,10 @@ def main():
data = {}
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
sim, frames = Simulation.load(file / 'data.squish')
sim, frames = Simulation.load(file)
data[delta] = {"norm": [], "time": [], "k": k}
for i, frame in enumerate(frames):
@ -36,24 +50,27 @@ def main():
data[delta]["time"].append(sim.step_size * i)
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):
ax.plot(np.log10(np.array(data[delta]["time"])+1), np.log10(data[delta]["norm"]),
label=f"k = {data[delta]['k']}")
ax.plot(
np.log10(np.array(data[delta]["time"]) + 1),
np.log10(data[delta]["norm"]),
label=f"k = {data[delta]['k']}",
)
fig.suptitle("Equilibrium Perturbations")
ax.grid(zorder=0)
# ax.set_xlim([0, 5])
ax.legend()
ax.set_xlabel("Log Time")
ax.set_ylabel("Log L2 Norm of Difference")
ax.set_xlabel("$\\log_{10}$ Time")
ax.set_ylabel("$\\log_{10}$ L2 Norm of Difference")
fig.savefig(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"
try:
main()

View File

@ -15,15 +15,22 @@ def order_process(domain: DomainParams) -> Tuple[float, float, float]:
energies = []
configs = order.configurations(domain)
for config in configs:
energies.append(2*domain.w*domain.h + \
2*math.pi*domain.n*(domain.r**2 - 2*domain.r*order.avg_radius(domain, config)))
energies.append(
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)
def get_ordered_energies(orig_domain: DomainParams, widths: np.ndarray) -> Dict:
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:
mins, maxes = {}, {}
@ -32,8 +39,12 @@ def get_ordered_energies(orig_domain: DomainParams, widths: np.ndarray) -> Dict:
maxes[res[0]] = res[2]
hashes = int(21 * i / len(widths))
print(f'Generating at width {res[0]:.02f}... |{"#"*hashes}{" "*(20-hashes)}|' + \
f' {i+1}/{len(widths)} completed.', flush=True, end='\r')
print(
f'Generating at width {res[0]:.02f}... |{"#"*hashes}{" "*(20-hashes)}|'
+ f" {i+1}/{len(widths)} completed.",
flush=True,
end="\r",
)
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]]:
sim, frames = Simulation.load(file / 'data.squish')
sim, frames = Simulation.load(file)
alls = []
for frame_info in frames:
alls.append([
alls.append(
[
frame_info["energy"],
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)
counts = sim.get_distinct()
distincts = []
for j, frame_info in enumerate(sim.frames):
distincts.append([
distincts.append(
[
frame_info["energy"],
np.var(frame_info["stats"]["avg_radius"]) <= 1e-8,
np.count_nonzero(frame_info["stats"]["site_edge_count"] != 6),
counts[j]
])
counts[j],
]
)
return sim.domain.w, alls, distincts
@ -80,11 +95,15 @@ def get_equilibria_data(filepath: Path) -> Tuple[Dict, numpy.ndarray, DomainPara
data["distinct"][res[0]] = res[2]
hashes = int(21 * i / len(files))
print(f'Loading simulations... |{"#"*hashes}{" "*(20-hashes)}|' + \
f' {i+1}/{len(files)} simulations loaded.', flush=True, end='\r')
print(
f'Loading simulations... |{"#"*hashes}{" "*(20-hashes)}|'
+ f" {i+1}/{len(files)} simulations loaded.",
flush=True,
end="\r",
)
print(flush=True)
sim, frames = Simulation.load(files[0] / 'data.squish')
sim, frames = Simulation.load(files[0])
widths = np.asarray(sorted(data["all"]))
domain = DomainParams(sim.domain.n, widths[-1], sim.domain.h, sim.domain.r)
return data, widths, domain
@ -95,16 +114,25 @@ def axis_settings(ax, widths):
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)
plt.subplots_adjust(0.07, 0.12, 0.97, 0.9)
def main():
# Loading arguments.
parser = argparse.ArgumentParser("Outputs width search data into diagrams")
parser.add_argument('sims_path', metavar='path/to/data',
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")
parser.add_argument(
"sims_path",
metavar="path/to/data",
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()
@ -121,10 +149,17 @@ def main():
all_disorder_count = []
for width in widths:
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)
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.title.set_text(f"Probability of Disorder - N{domain.n}")
ax.set_xlabel("Width")
@ -133,7 +168,6 @@ def main():
ax.set_yticks(np.arange(boa_y_min, 100.01, 2.5))
fig.savefig(fig_folder / "Probability of Disorder.png")
# Density of States diagram.
fig, ax = plt.subplots(figsize=(16, 8))
distinct_ordered, distinct_unordered = [], []
@ -143,13 +177,13 @@ def main():
distinct_unordered.append(equal_shape.count(False))
ax2 = ax.twinx()
ax.plot(widths, distinct_unordered, label="Unordered Equilibria", color='C0')
ax2.plot(widths, distinct_ordered, label="Ordered Equilibria", color='C1')
ax.plot(widths, distinct_unordered, label="Unordered Equilibria", color="C0")
ax2.plot(widths, distinct_ordered, label="Ordered Equilibria", color="C1")
axis_settings(ax, widths)
ax.title.set_text(f"Density of States - N{domain.n}")
ax.set_xlabel("Width")
ax.set_ylabel("Number of States (Disordered)", color='C0')
ax2.set_ylabel("Number of States (Ordered)", color='C1')
ax.set_ylabel("Number of States (Disordered)", color="C0")
ax2.set_ylabel("Number of States (Ordered)", color="C1")
dos_y_max_unorder = 1.05 * max(distinct_unordered)
dos_y_max_order = 1.05 * max(distinct_ordered)
@ -157,7 +191,6 @@ def main():
# 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))
fig.savefig(fig_folder / "Density Of States.png")
# Defect density diagram
@ -165,7 +198,10 @@ def main():
defects = []
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)
axis_settings(ax, widths)
@ -175,7 +211,6 @@ def main():
ax.set_yticks(np.arange(0, 1 + max(defects), 0.5))
fig.savefig(fig_folder / "Defects.png")
# Bifurcation diagram
fig, ax = plt.subplots(figsize=(16, 8))
@ -204,15 +239,19 @@ def main():
min_unorder_off = min_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, min_unorder_off, color='C0')
ax.plot(widths, max_unorder_off, color='C0', linestyle='dotted')
ax.plot(widths, min_unorder_off, color="C0")
ax.plot(widths, max_unorder_off, color="C0", linestyle="dotted")
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:
ax.scatter(widths[i], 0,
marker='X', color="blue", s=50, zorder=4)
ax.scatter(widths[i], 0, marker="X", color="blue", s=50, zorder=4)
# ax.scatter(widths[i], max_unorder[i] - offset[i],
# marker='X', edgecolors="blue", facecolors='none', s=100, zorder=4)
@ -220,13 +259,16 @@ def main():
ax.set_xlabel("Width")
ax.set_ylabel("Reduced Energy")
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)))
fig.savefig(fig_folder / "Bifurcation.png")
print(f"Wrote to {fig_folder}.")
if __name__ == '__main__':
if __name__ == "__main__":
os.environ["QT_LOGGING_RULES"] = "*=false"
try:
main()

View File

@ -10,16 +10,20 @@ OUTPUT_DIR = Path("squish_output")
STR_TO_ENERGY = {
"area": AreaEnergy,
"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
width, height = round(sim.domain.w, prec), round(sim.domain.h, prec)
base_path = f"{fol}/{energy}{sim.title_str} - " + \
f"N{sim.domain.n} - {width:.{prec}f}x{height:.{prec}f}"
base_path = (
f"{fol}/{energy}{sim.title_str} - "
+ f"N{sim.domain.n} - {width:.{prec}f}x{height:.{prec}f}"
)
i = 1
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:
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.dim = np.array([self.w, self.h])
def __iter__(self) -> Iterator:
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:
if isinstance(mode, str):
try:
self.mode = STR_TO_ENERGY[mode.lower()]
except KeyError:
raise ValueError(f"\'{mode}\' is not a valid energy!")
raise ValueError(f"'{mode}' is not a valid energy!")
else:
if mode is not VoronoiContainer and issubclass(mode, VoronoiContainer):
raise ValueError("Provided class is not a valid energy!")
self.mode = mode
@property
def attr_str(self) -> str:
return self.mode.attr_str
@property
def title_str(self) -> str:
return self.mode.title_str

View File

@ -8,7 +8,10 @@ from multiprocessing import Pool, cpu_count
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:
"""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:
if sim is not None:
@ -33,11 +35,9 @@ class SimData:
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 slice(self, indices: List[int]) -> SimData:
new_data = SimData(None)
new_data.path = self.path
@ -49,17 +49,24 @@ class SimData:
return new_data
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]:
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.
bounds (Optional[Tuple[float, float]]): upper and lower bounds of the
histogram. This will automatically take the minimum and maximum value
if 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.
@ -67,6 +74,7 @@ class SimData:
Tuple[numpy.ndarray, numpy.ndarray]: the histogram and its bins.
"""
if cumul:
values = np.concatenate([f[stat] for f in self.stats[: (i + 1)]])
else:
@ -81,7 +89,9 @@ class SimData:
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)])
bin_list = np.array(
[(bin_edges[i] + bin_edges[i + 1]) / 2 for i in range(len(bin_edges) - 1)]
)
if avg and cumul:
return hist / (i + 1), bin_list
@ -99,15 +109,15 @@ class Diagram:
"""
__slots__ = ['sim', 'diagrams', 'cumulative']
__slots__ = ["sim", "diagrams", "cumulative"]
def __init__(self, sim: Simulation, diagrams: np.ndarray, cumulative: bool = False) -> None:
def __init__(
self, sim: Simulation, diagrams: np.ndarray, cumulative: bool = False
) -> None:
self.sim = SimData(sim)
self.diagrams = np.atleast_2d(diagrams)
self.cumulative = cumulative
def generate_frame(self, frame: int, mode: str, fol: str, name: str = None) -> None:
if mode not in ["save", "open"]:
raise ValueError("Not a valid mode for diagrams!")
@ -115,14 +125,14 @@ class Diagram:
shape = self.diagrams.shape
fig, axes = plt.subplots(*shape, figsize=(shape[1] * 8, shape[0] * 8))
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:
axes = np.atleast_2d(axes)
it = np.nditer(self.diagrams, flags=["multi_index"])
for diagram in it:
if diagram == "":
continue
getattr(self, str(diagram) + '_plot')(frame, axes[it.multi_index])
getattr(self, str(diagram) + "_plot")(frame, axes[it.multi_index])
plt.tight_layout()
if name is None:
@ -134,33 +144,36 @@ class Diagram:
elif mode == "show":
plt.show()
def voronoi_plot(self, i: int, ax: AxesSubplot) -> None:
domain = self.sim.domains[i]
n, w, h = domain.n, domain.w, domain.h
scale = 1.5
area = n <= 60
voronoi_plot_2d(self.sim.voronois[i], ax, show_vertices=False, point_size = 7-n/100)
ax.plot([-w, 2*w], [0, 0], 'r')
ax.plot([-w, 2*w], [h, h], 'r')
ax.plot([0,0], [-h, 2*h], 'r')
ax.plot([w, w], [-h, 2*h], 'r')
ax.axis('equal')
voronoi_plot_2d(
self.sim.voronois[i], ax, show_vertices=False, point_size=7 - n / 100
)
ax.plot([-w, 2 * w], [0, 0], "r")
ax.plot([-w, 2 * w], [h, h], "r")
ax.plot([0, 0], [-h, 2 * h], "r")
ax.plot([w, w], [-h, 2 * h], "r")
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")
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": []}}
for j in range(n):
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:
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)
if self.sim.stats[i]["site_edge_count"][j] == 5:
@ -173,17 +186,22 @@ class Diagram:
ax.scatter(defects[5]["x"], defects[5]["y"], marker="p", color="red")
ax.scatter(defects[7]["x"], defects[7]["y"], marker="*", color="red")
ax.text(0.05, 0.95, f'Energy: {self.sim.energies[i]}', transform=ax.transAxes, fontsize=14,
verticalalignment='top', bbox=props)
ax.text(
0.05,
0.95,
f"Energy: {self.sim.energies[i]}",
transform=ax.transAxes,
fontsize=14,
verticalalignment="top",
bbox=props,
)
def energy_plot(self, i: int, ax: AxesSubplot) -> None:
ax.set_xlim([0, len(self.sim)])
energies = self.sim.energies[: (i + 1)]
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)
# min_value = round(self.sim[-1].energy)
# diff = max_value-min_value
@ -192,12 +210,11 @@ class Diagram:
ax.set_ylabel("Energy")
ax.grid()
def site_areas_plot(self, i: int, ax: AxesSubplot) -> None:
y, x = self.sim.hist("site_areas", i, cumul=self.cumulative, avg=True)
ax.bar(x, y, width=0.8 * (x[1] - x[0]))
ax.title.set_text('Site Areas')
ax.title.set_text("Site Areas")
ax.set_xlabel("Area")
ax.set_ylabel("Average Occurances")
ax.set_xticks(x)
@ -207,24 +224,26 @@ class Diagram:
# if color != 'C0':
# xtick.set_color(color)
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.title.set_text('Edges per Site')
ax.title.set_text("Edges per Site")
ax.set_xlabel("Number of Edges")
ax.set_ylabel("Average Occurances")
ax.set_xticks(x)
ax.set_xticklabels([int(z) for z in x])
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
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.title.set_text('Isoparametric Values')
ax.title.set_text("Isoparametric Values")
ax.set_xlabel("Isoparametric Value")
ax.set_ylabel("Average Occurances")
ax.set_xticks(x)
@ -234,83 +253,84 @@ class Diagram:
# if color != 'C0':
# xtick.set_color(color)
def site_energies_plot(self, i: int, ax: AxesSubplot) -> None:
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.title.set_text('Site Energies')
ax.title.set_text("Site Energies")
ax.set_xlabel("Energy")
ax.set_ylabel("Average Occurances")
ax.set_xticks(x)
ax.ticklabel_format(useOffset=False)
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
def avg_radius_plot(self, i: int, ax: AxesSubplot) -> None:
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.title.set_text('Site Average Radii')
ax.title.set_text("Site Average Radii")
ax.set_xlabel("Average Radius")
ax.set_ylabel("Average Occurances")
ax.set_xticks(x)
ax.ticklabel_format(useOffset=False)
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
def isoparam_avg_plot(self, i: int, ax: AxesSubplot) -> None:
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.title.set_text('Site Isoperimetric Averages')
ax.title.set_text("Site Isoperimetric Averages")
ax.set_xlabel("Isoperimetric Average")
ax.set_ylabel("Average Occurances")
ax.set_xticks(x)
ax.ticklabel_format(useOffset=False)
ax.yaxis.set_major_locator(MaxNLocator(integer=True))
def edge_lengths_plot(self, i: int, ax: AxesSubplot) -> None:
y, x = self.sim.hist("edge_lengths", i, 30, cumul=self.cumulative, avg=True)
ax.bar(x, y, width=0.8 * (x[1] - x[0]))
ax.title.set_text('Edge Lengths')
ax.title.set_text("Edge Lengths")
ax.set_xlabel("Length")
ax.set_ylabel("Average Occurances")
ax.set_xticks(x)
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.yaxis.set_major_locator(MaxNLocator(integer=True))
# for xtick, color in zip(ax.get_xticklabels(), lengths_bar[2]):
# if color != 'C0':
# xtick.set_color(color)
def eigs_plot(self, i: int, ax: AxesSubplot) -> None:
try:
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")
except KeyError:
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_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)
self.sim.path.mkdir(exist_ok=True)
(self.sim.path / fol).mkdir(exist_ok=True)
combo_list = []
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.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.
self.sim = self.sim.slice([])
@ -319,8 +339,7 @@ class Diagram:
pass
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:
if mode not in ["use_all", "sample"]:
@ -334,23 +353,27 @@ class Diagram:
frames = list(range(len(self.sim)))
fps = len(self.sim) / time
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')
path = self.sim.path / 'simulation.mp4'
self.render_frames(frames, "temp")
path = self.sim.path / "simulation.mp4"
print("Assembling MP4...", flush=True)
os.system(f'ffmpeg -hide_banner -loglevel error -r {fps} -i' + \
f' \"{self.sim.path}/temp/img%05d.png\"' + \
f' -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -vf' + \
f' "scale=trunc(iw/2)*2:trunc(ih/2)*2" -f mp4 "{path}"')
os.system(
f"ffmpeg -hide_banner -loglevel error -r {fps} -i"
+ f' "{self.sim.path}/temp/img%05d.png"'
+ f" -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -vf"
+ f' "scale=trunc(iw/2)*2:trunc(ih/2)*2" -f mp4 "{path}"'
)
# Remove files.
for i in range(len(frames)):
os.remove(self.sim.path / f"temp/img{i:05}.png")
os.rmdir(self.sim.path / 'temp')
print(f'Wrote to \"{path}\".', flush=True)
os.rmdir(self.sim.path / "temp")
print(f'Wrote to "{path}".', flush=True)
def render_frame_range(combo: Tuple[Diagram, str, int, int, int]) -> None:
@ -359,5 +382,9 @@ def render_frame_range(combo: Tuple[Diagram, str, int, int, int]) -> None:
self.generate_frame(i, "save", fol, f"img{i+offset:05}.png")
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')
print(
f'Generating frames... |{"#"*hashes}{" "*(20-hashes)}|'
+ f" {i}/{num_frames} frames rendered.",
flush=True,
end="\r",
)

View File

@ -6,6 +6,7 @@ from math import gcd, sqrt, log, tan, atan, pi
Config = Tuple[int, int]
def configurations(domain: DomainParams) -> List[Config]:
n, w, h = domain.n, domain.w, domain.h
valid = []
@ -18,7 +19,12 @@ def configurations(domain: DomainParams) -> List[Config]:
if gcd(eq_x, eq_y) != 1:
continue
vecs = configs[i]*np.dstack((w*mults, h*mults)).swapaxes(0,1)/n % domain.dim
vecs = (
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()
@ -28,7 +34,9 @@ def configurations(domain: DomainParams) -> List[Config]:
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
q1 = sites(domain, config)
v = q1[1]
@ -39,9 +47,13 @@ def get_config_generators(domain: DomainParams, config: Config) -> Tuple[Config,
tol = 1e-3
vdot = np.matmul(all_sites, v)
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)
@ -57,7 +69,11 @@ def area(domain: DomainParams, config: Config) -> float:
v, w = np.array(v), np.array(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:
@ -65,12 +81,15 @@ def avg_radius(domain: DomainParams, config: Config) -> float:
v, w = np.array(v), np.array(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)) + \
avg_rp(mag(v-w),2*mag((v+w)/2-c)))
return 2 * (
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:
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:

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, \
name: Optional[str, int] = None) -> None:
def __init__(
self, domain: DomainParams, energy: Energy, name: Optional[str, int] = None
) -> None:
self.domain, self.energy = domain, energy
self.frames = []
@ -35,18 +36,15 @@ class Simulation:
else:
self.path = OUTPUT_DIR / name
def __iter__(self) -> Iterator:
return iter(self.frames)
def __getitem__(self, key: int) -> Energy:
return self.frames[key]
def __len__(self) -> int:
return len(self.frames)
def add_frame(self, points: Optional[numpy.ndarray] = None) -> None:
if points is None:
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))
def get_distinct(self) -> List[int]:
"""Gets the distinct configurations based on the average radii of the sites.
and returns the number of configurations for each distinct configuration.
@ -67,7 +64,6 @@ class Simulation:
distinct_avg_radii, distinct_count, new_frames = [], [], []
for frame in self.frames:
try:
stats = frame.stats
@ -90,38 +86,35 @@ class Simulation:
self.frames = new_frames
return distinct_count
def save(self, info: Dict, overwrite: bool = False) -> None:
OUTPUT_DIR.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)
def save_all(self) -> None:
self.save(self.initial_data, True)
for i in range(len(self.frames)):
self.save(self.frame_data(i))
def frame_data(self, index: int) -> None:
f = self[index]
info = {
"arr": f.site_arr,
"domain": (f.n, f.w, f.h, f.r),
"energy": f.energy,
"stats": f.stats
"stats": f.stats,
}
return info
@staticmethod
def load(path: str) -> Tuple[Simulation, Generator]:
path = Path(path)
def frames() -> Dict:
with open(path / 'data.squish', 'rb') as infile:
with open(path / "data.squish", "rb") as infile:
first = True
while True:
try:
@ -133,17 +126,18 @@ class Simulation:
except EOFError:
break
with open(path / 'data.squish', 'rb') as infile:
with open(path / "data.squish", "rb") as infile:
sim_info = pickle.load(infile)
domain = DomainParams(*sim_info["domain"])
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
return sim, frames()
@staticmethod
def from_file(path: str) -> Simulation:
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"
title_str = "Flow"
def __init__(self, domain: DomainParams, energy: Energy, step_size: float, thres: float,
adaptive: bool, name: Optional[str] = None) -> None:
def __init__(
self,
domain: DomainParams,
energy: Energy,
step_size: float,
thres: float,
adaptive: bool,
name: Optional[str] = None,
) -> None:
super().__init__(domain, energy, name=name)
self.step_size, self.thres, self.adaptive = step_size, thres, adaptive
@property
def initial_data(self) -> Dict:
info = {
@ -185,16 +185,23 @@ class Flow(Simulation):
"energy": self.energy.attr_str,
"step_size": self.step_size,
"thres": self.thres,
"adaptive": self.adaptive
"adaptive": self.adaptive,
}
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,
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
@ -217,7 +224,7 @@ class Flow(Simulation):
error = change - grad * self.step_size
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:
del self.frames[0]
@ -227,10 +234,12 @@ class Flow(Simulation):
i += 1
if (log and i % log_steps == 0) or stop:
print(f'Iteration: {i:05} | Energy: {frame.energy: .5f}' + \
f' | Gradient: {grad_norm:.8f} | Step: {self.step_size: .5f} | ' + \
f'Time: {end-start: .3f} |', flush=True)
print(
f"Iteration: {i:05} | Energy: {frame.energy: .5f}"
+ f" | Gradient: {grad_norm:.8f} | Step: {self.step_size: .5f} | "
+ f"Time: {end-start: .3f} |",
flush=True,
)
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"
title_str = "Search"
def __init__(self, domain: DomainParams, energy: Energy, step_size: float, thres: float,
adaptive: bool, kernel_step: float, count: int,
name: Optional[str] = None) -> None:
def __init__(
self,
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)
self.step_size, self.thres, self.adaptive = step_size, thres, adaptive
self.kernel_step, self.count = kernel_step, count
@property
def initial_data(self) -> Dict:
info = {
@ -271,39 +287,53 @@ class Search(Simulation):
"thres": self.thres,
"adaptive": self.adaptive,
"kernel_step": self.kernel_step,
"count": self.count
"count": self.count,
}
return info
def run(self, save: bool, log: bool, 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)
def run(
self,
save: bool,
log: bool,
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):
# 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.run(False, log, log_steps)
self.frames.append(sim[-1])
if save: self.save(self.frame_data(i))
if log: print(f'Equilibrium: {i:04}\n', flush=True)
if save:
self.save(self.frame_data(i))
if log:
print(f"Equilibrium: {i:04}\n", flush=True)
# Get Hessian,and check nullity. If > 2, perturb.
hess = self.frames[i].hessian(10e-5)
eigs = np.sort(np.linalg.eig(hess)[0])
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:
new_sites = None
else:
print("Warning: Nullity > 2. Expected if AreaEnergy.", flush=True)
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(
(self.domain.n, 2)
) # Random vector.
new_sites = self.frames[i].add_sites(self.kernel_step * vec)
@ -312,7 +342,7 @@ class Shrink(Simulation):
Attributes:
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.
frames (List[VoronoiContainer]): stores frames of the simulation.
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"
title_str = "Shrink"
def __init__(self, domain: DomainParams, energy: Energy, step_size: float, thres: float,
adaptive: bool, delta: float, stop_width: float,
name: Optional[str] = None) -> None:
def __init__(
self,
domain: DomainParams,
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)
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
def initial_data(self) -> Dict:
@ -346,37 +385,45 @@ class Shrink(Simulation):
"thres": self.thres,
"adaptive": self.adaptive,
"delta": self.delta,
"stop_width": self.stop_width
"stop_width": self.stop_width,
}
return info
def run(self, save: bool, log: bool, 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)
def run(
self,
save: bool,
log: bool,
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
i = 0
while width >= self.stop_width:
# Get to equilibrium.
new_domain = DomainParams(self.domain.n, width, self.domain.h, self.domain.r)
sim = Flow(new_domain, self.energy, self.step_size, self.thres, self.adaptive)
new_domain = DomainParams(
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.run(False, log, log_steps)
new_sites = sim[-1].site_arr
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
i += 1
STR_TO_SIM = {
"flow": Flow,
"search": Search,
"shrink": Shrink
}
STR_TO_SIM = {"flow": Flow, "search": Search, "shrink": Shrink}

View File

@ -13,11 +13,12 @@ dia_presets = {
"energy": [["voronoi", "energy"]],
"stats": [
["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:
"""Checks container for the necessary items, and raises
an error if the parameter is not found.
@ -25,41 +26,65 @@ def check_params(container: Dict, needed: List[str], valid: Dict) -> None:
Args:
container (Dict): contains the submitted parameters.
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:
if need not in container:
raise ValueError(f"Parameter \'{need}\' is required.")
raise ValueError(f"Parameter '{need}' is required.")
if need in valid:
if type(valid[need]) is list:
if container[need] not in valid[need]:
raise ValueError(f"Parameter \'{need}\' must be one of these values: " + \
f"{str(valid[need])[1:-1]}.")
raise ValueError(
f"Parameter '{need}' must be one of these values: "
+ f"{str(valid[need])[1:-1]}."
)
elif valid[need] == "positive":
if container[need] < 0:
raise ValueError(f"Parameter \'{need}\' must be positive.")
raise ValueError(f"Parameter '{need}' must be positive.")
def main():
# Loading configuration and settings.
parser = argparse.ArgumentParser("Squish")
parser.add_argument('sim_conf', metavar='/path/to/config.json',
help="configuration file for a simulation")
parser.add_argument('-q', '--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(
"sim_conf",
metavar="/path/to/config.json",
help="configuration file for a simulation",
)
parser.add_argument(
"-q",
"--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('--width', dest='w', type=float, help="width 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('--energy', dest='energy', help="energy type of system")
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("--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("--energy", dest="energy", help="energy type of system")
args = parser.parse_args()
do_sim(args, args.input_file)
@ -69,38 +94,63 @@ def do_sim(args, file):
with open(args.sim_conf) as f:
params = json.load(f)
check_params(params, ["domain", "simulation"], {})
dmn_params, sim_params = params["domain"], params["simulation"]
overrides = {args.n: "n_objects", args.w: "width", args.h: "height", args.r: "natural_radius",
args.energy: "energy"}
overrides = {
args.n: "n_objects",
args.w: "width",
args.h: "height",
args.r: "natural_radius",
args.energy: "energy",
}
for arg, arg_name in overrides.items():
if arg is not None:
dmn_params[arg_name] = arg
check_params(dmn_params, ["n_objects", "width", "height", "natural_radius", "energy"], {
"n_objects": "positive", "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"])
check_params(
dmn_params,
["n_objects", "width", "height", "natural_radius", "energy"],
{
"n_objects": "positive",
"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"])
points = None
if "points" in dmn_params:
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)
else:
points = np.asarray(dmn_params["points"])
check_params(sim_params, ["mode", "step_size", "threshold", "save_sim", "adaptive"], {
"mode": ["flow", "search", "shrink"], "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"]
check_params(
sim_params,
["mode", "step_size", "threshold", "save_sim", "adaptive"],
{
"mode": ["flow", "search", "shrink"],
"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")
@ -108,17 +158,37 @@ def do_sim(args, file):
if mode == "flow":
sim = Flow(domain, energy, step, thres, adaptive, name=name)
elif mode == "search":
check_params(sim_params, ["manifold_step_size", "eq_stop_count"], {
"manifold_step_size": "positive", "eq_stop_count": "positive"
})
sim = Search(domain, energy, step, thres, adaptive, sim_params["manifold_step_size"],
sim_params["eq_stop_count"], name=name)
check_params(
sim_params,
["manifold_step_size", "eq_stop_count"],
{"manifold_step_size": "positive", "eq_stop_count": "positive"},
)
sim = Search(
domain,
energy,
step,
thres,
adaptive,
sim_params["manifold_step_size"],
sim_params["eq_stop_count"],
name=name,
)
elif mode == "shrink":
check_params(sim_params, ["width_change", "width_stop"], {
"width_change": "positive", "width_stop": "positive"
})
sim = Shrink(domain, energy, step, thres, adaptive, sim_params["width_change"],
sim_params["width_stop"], name=name)
check_params(
sim_params,
["width_change", "width_stop"],
{"width_change": "positive", "width_stop": "positive"},
)
sim = Shrink(
domain,
energy,
step,
thres,
adaptive,
sim_params["width_change"],
sim_params["width_stop"],
name=name,
)
else:
sim = Simulation.from_file(file)
@ -126,12 +196,12 @@ def do_sim(args, file):
if "diagram" in params:
save_diagram = True
dia_params = params["diagram"]
check_params(dia_params, ["filetype", "figures"], {
"filetype": ["img", "mp4"]
})
check_params(dia_params, ["filetype", "figures"], {"filetype": ["img", "mp4"]})
if dia_params["filetype"] == "mp4":
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:
dia_params["figures"] = np.asarray(dia_presets[dia_params["figures"]])