Alexandre B A Villares 2020-08-21 18:40:47 -03:00
rodzic 538d5ee4bf
commit 14e138ef80
5 zmienionych plików z 1947 dodań i 0 usunięć

Wyświetl plik

@ -0,0 +1,171 @@
# -*- coding: utf-8 -*-
ROTATION = {0: 0,
BOTTOM: 0,
DOWN: 0,
1: HALF_PI,
LEFT: HALF_PI,
2: PI,
TOP: PI,
UP: PI,
3: PI + HALF_PI,
RIGHT: PI + HALF_PI,
BOTTOM + RIGHT: 0,
DOWN + RIGHT: 0,
DOWN + LEFT: HALF_PI,
BOTTOM + LEFT: HALF_PI,
TOP + LEFT: PI,
UP + LEFT: PI,
TOP + RIGHT: PI + HALF_PI,
UP + RIGHT: PI + HALF_PI,
}
def quarter_circle(x, y, radius, quadrant):
circle_arc(x, y, radius, ROTATION[quadrant], HALF_PI)
def half_circle(x, y, radius, quadrant):
circle_arc(x, y, radius, ROTATION[quadrant], PI)
def circle_arc(x, y, radius, start_ang, sweep_ang):
arc(x, y, radius * 2, radius * 2, start_ang, start_ang + sweep_ang)
def poly_arc(x, y, radius, start_ang, sweep_ang, num_points=2):
angle = sweep_ang / int(num_points)
a = start_ang
with beginShape():
while a <= start_ang + sweep_ang:
sx = x + cos(a) * radius
sy = y + sin(a) * radius
vertex(sx, sy)
a += angle
def arc_poly(x, y, d, _, start_ang, end_ang, num_points=5):
sweep_ang = end_ang - start_ang
angle = sweep_ang / int(num_points)
a = start_ang
with beginShape():
while a <= end_ang:
sx = x + cos(a) * d / 2
sy = y + sin(a) * d / 2
vertex(sx, sy)
a += angle
def bar(x1, y1, x2, y2, thickness=None, shorter=0, ends=(1, 1)):
"""
O código para fazer as barras, dois pares (x, y),
um parâmetro de encurtamento: shorter
"""
L = dist(x1, y1, x2, y2)
if not thickness:
thickness = 10
with pushMatrix():
translate(x1, y1)
angle = atan2(x1 - x2, y2 - y1)
rotate(angle)
offset = shorter / 2
line(thickness / 2, offset, thickness / 2, L - offset)
line(-thickness / 2, offset, -thickness / 2, L - offset)
if ends[0]:
half_circle(0, offset, thickness / 2, UP)
if ends[1]:
half_circle(0, L - offset, thickness / 2, DOWN)
def var_bar(p1x, p1y, p2x, p2y, r1, r2=None):
"""
Tangent/tangent shape on 2 circles of arbitrary radius
"""
if r2 is None:
r2 = r1
#line(p1x, p1y, p2x, p2y)
d = dist(p1x, p1y, p2x, p2y)
ri = r1 - r2
if d > abs(ri):
rid = (r1 - r2) / d
if rid > 1:
rid = 1
if rid < -1:
rid = -1
beta = asin(rid) + HALF_PI
with pushMatrix():
translate(p1x, p1y)
angle = atan2(p1x - p2x, p2y - p1y)
rotate(angle + HALF_PI)
x1 = cos(beta) * r1
y1 = sin(beta) * r1
x2 = cos(beta) * r2
y2 = sin(beta) * r2
#print((d, beta, ri, x1, y1, x2, y2))
beginShape()
b_arc(0, 0, r1 * 2, r1 * 2,
-beta - PI, beta - PI, mode=2)
b_arc(d, 0, r2 * 2, r2 * 2,
beta - PI, PI - beta, mode=2)
endShape(CLOSE)
else:
ellipse(p1x, p1y, r1 * 2, r1 * 2)
ellipse(p2x, p2y, r2 * 2, r2 * 2)
def b_arc(cx, cy, w, h, start_angle, end_angle, mode=0):
"""
A bezier approximation of an arc
using the same signature as the original Processing arc()
mode: 0 "normal" arc, using beginShape() and endShape()
1 "middle" used in recursive call of smaller arcs
2 "naked" like normal, but without beginShape() and endShape()
for use inside a larger PShape
"""
theta = end_angle - start_angle
# Compute raw Bezier coordinates.
if mode != 1 or abs(theta) < HALF_PI:
x0 = cos(theta / 2.0)
y0 = sin(theta / 2.0)
x3 = x0
y3 = 0 - y0
x1 = (4.0 - x0) / 3.0
if y0 != 0:
y1 = ((1.0 - x0) * (3.0 - x0)) / (3.0 * y0) # y0 != 0...
else:
y1 = 0
x2 = x1
y2 = 0 - y1
# Compute rotationally-offset Bezier coordinates, using:
# x' = cos(angle) * x - sin(angle) * y
# y' = sin(angle) * x + cos(angle) * y
bezAng = start_angle + theta / 2.0
cBezAng = cos(bezAng)
sBezAng = sin(bezAng)
rx0 = cBezAng * x0 - sBezAng * y0
ry0 = sBezAng * x0 + cBezAng * y0
rx1 = cBezAng * x1 - sBezAng * y1
ry1 = sBezAng * x1 + cBezAng * y1
rx2 = cBezAng * x2 - sBezAng * y2
ry2 = sBezAng * x2 + cBezAng * y2
rx3 = cBezAng * x3 - sBezAng * y3
ry3 = sBezAng * x3 + cBezAng * y3
# Compute scaled and translated Bezier coordinates.
rx, ry = w / 2.0, h / 2.0
px0 = cx + rx * rx0
py0 = cy + ry * ry0
px1 = cx + rx * rx1
py1 = cy + ry * ry1
px2 = cx + rx * rx2
py2 = cy + ry * ry2
px3 = cx + rx * rx3
py3 = cy + ry * ry3
# Debug points... comment this out!
# stroke(0)
# ellipse(px3, py3, 15, 15)
# ellipse(px0, py0, 5, 5)
# Drawing
if mode == 0: # 'normal' arc (not 'middle' nor 'naked')
beginShape()
if mode != 1: # if not 'middle'
vertex(px3, py3)
if abs(theta) < HALF_PI:
bezierVertex(px2, py2, px1, py1, px0, py0)
else:
# to avoid distortion, break into 2 smaller arcs
b_arc(cx, cy, w, h, start_angle, end_angle - theta / 2.0, mode=1)
b_arc(cx, cy, w, h, start_angle + theta / 2.0, end_angle, mode=1)
if mode == 0: # end of a 'normal' arc
endShape()

Wyświetl plik

@ -0,0 +1,338 @@
#*- coding: utf-8 -*-
"""
A simple Python graph class, demonstrating the essential facts and functionalities of graphs
based on https://www.python-course.eu/graphs_python.php and https://www.python.org/doc/essays/graphs/
"""
from random import choice
class Graph(object):
def __init__(self, graph_dict=None):
"""
Initialize a graph object with dictionary provided,
if none provided, create an empty one.
"""
if graph_dict is None:
graph_dict = {}
self.__graph_dict = graph_dict
def __len__(self):
return len(self.__graph_dict)
def vertices(self):
"""Return the vertices of graph."""
return list(self.__graph_dict.keys())
def edges(self):
"""Return the edges of graph """
return self.__generate_edges()
def add_vertex(self, vertex):
"""
If the vertex "vertex" is not in self.__graph_dict,
add key "vertex" with an empty list as a value,
otherwise, do nothing.
"""
if vertex not in self.__graph_dict:
self.__graph_dict[vertex] = []
def add_edge(self, edge):
"""
Assuming that edge is of type set, tuple or list;
add edge between vertices. Can add multiple edges!
"""
edge = set(edge)
vertex1 = edge.pop()
if edge:
# not a loop
vertex2 = edge.pop()
if vertex1 in self.__graph_dict:
self.__graph_dict[vertex1].append(vertex2)
else:
self.__graph_dict[vertex1] = [vertex2]
if vertex2 in self.__graph_dict:
self.__graph_dict[vertex2].append(vertex1)
else:
self.__graph_dict[vertex2] = [vertex1]
else:
# a loop
if vertex1 in self.__graph_dict:
self.__graph_dict[vertex1].append(vertex1)
else:
self.__graph_dict[vertex1] = [vertex1]
def remove_vertex(self, vert):
del self.__graph_dict[vert]
for k in self.__graph_dict.keys():
if vert in self.__graph_dict[k]:
self.__graph_dict[k].remove(vert)
def remove_edge(self, edge):
edge = set(edge)
vertex1 = edge.pop()
if edge:
vertex2 = edge.pop()
self.__graph_dict[vertex1].remove(vertex2)
self.__graph_dict[vertex2].remove(vertex1)
else:
self.__graph_dict[vertex1].remove(vertex1)
def __generate_edges(self):
"""
Generate the edges, represented as sets with one
(a loop back to the vertex) or two vertices.
"""
edges = []
for vertex in self.__graph_dict:
for neighbour in self.__graph_dict[vertex]:
if {neighbour, vertex} not in edges:
edges.append({vertex, neighbour})
return edges
def __str__(self):
res = "vertices: "
for k in self.__graph_dict:
res += str(k) + " "
res += "\nedges: "
for edge in self.__generate_edges():
res += str(edge) + " "
return res
def find_isolated_vertices(self):
"""
Return a list of isolated vertices.
"""
graph = self.__graph_dict
isolated = []
for vertex in graph:
print(isolated, vertex)
if not graph[vertex]:
isolated += [vertex]
return isolated
def find_path(self, start_vertex, end_vertex, path=[]):
"""
Find a path from start_vertex to end_vertex in graph.
"""
graph = self.__graph_dict
path = path + [start_vertex]
if start_vertex == end_vertex:
return path
if start_vertex not in graph:
return None
for vertex in graph[start_vertex]:
if vertex not in path:
extended_path = self.find_path(vertex,
end_vertex,
path)
if extended_path:
return extended_path
return None
def find_all_paths(self, start_vertex, end_vertex, path=[]):
"""
Find all paths from start_vertex to end_vertex.
"""
graph = self.__graph_dict
path = path + [start_vertex]
if start_vertex == end_vertex:
return [path]
if start_vertex not in graph:
return []
paths = []
for vertex in graph[start_vertex]:
if vertex not in path:
extended_paths = self.find_all_paths(vertex,
end_vertex,
path)
for p in extended_paths:
paths.append(p)
return paths
def is_connected(self,
vertices_encountered=None,
start_vertex=None):
"""Find if the graph is connected."""
if vertices_encountered is None:
vertices_encountered = set()
gdict = self.__graph_dict
vertices = list(gdict.keys()) # "list" necessary in Python 3
if not start_vertex:
# chosse a vertex from graph as a starting point
start_vertex = vertices[0]
vertices_encountered.add(start_vertex)
if len(vertices_encountered) != len(vertices):
for vertex in gdict[start_vertex]:
if vertex not in vertices_encountered:
if self.is_connected(vertices_encountered, vertex):
return True
else:
return True
return False
def vertex_degree(self, vertex):
"""
Return the number of edges connecting to a vertex (the number of adjacent vertices).
Loops are counted double, i.e. every occurence of vertex in the list of adjacent vertices.
"""
adj_vertices = self.__graph_dict[vertex]
degree = len(adj_vertices) + adj_vertices.count(vertex)
return degree
def degree_sequence(self):
"""Calculates the degree sequence."""
seq = []
for vertex in self.__graph_dict:
seq.append(self.vertex_degree(vertex))
seq.sort(reverse=True)
return tuple(seq)
@staticmethod
def is_degree_sequence(sequence):
"""
Return True, if the sequence is a degree sequence (non-increasing),
otherwise return False.
"""
return all(x >= y for x, y in zip(sequence, sequence[1:]))
def delta(self):
"""Find minimum degree of vertices."""
min = 100000000
for vertex in self.__graph_dict:
vertex_degree = self.vertex_degree(vertex)
if vertex_degree < min:
min = vertex_degree
return min
def Delta(self):
"""Finde maximum degree of vertices."""
max = 0
for vertex in self.__graph_dict:
vertex_degree = self.vertex_degree(vertex)
if vertex_degree > max:
max = vertex_degree
return max
def density(self):
"""Calculate the graph density."""
g = self.__graph_dict
V = len(g.keys())
E = len(self.edges())
return 2.0 * E / (V * (V - 1))
def diameter(self):
"""Calculates the graph diameter."""
v = self.vertices()
pairs = [
(v[i],
v[j]) for i in range(
len(v)) for j in range(
i + 1,
len(v) - 1)]
smallest_paths = []
for (s, e) in pairs:
paths = self.find_all_paths(s, e)
smallest = sorted(paths, key=len)[0]
smallest_paths.append(smallest)
smallest_paths.sort(key=len)
# longest path is at the end of list,
# i.e. diameter corresponds to the length of this path
diameter = len(smallest_paths[-1]) - 1
return diameter
@staticmethod
def erdoes_gallai(dsequence):
"""
Check if Erdoes-Gallai inequality condition is fullfilled.
"""
if sum(dsequence) % 2:
# sum of sequence is odd
return False
if Graph.is_degree_sequence(dsequence):
for k in range(1, len(dsequence) + 1):
left = sum(dsequence[:k])
right = k * (k - 1) + sum([min(x, k) for x in dsequence[k:]])
if left > right:
return False
else:
# sequence is increasing
return False
return True
# Code by Eryk Kopczyński
def find_shortest_path(self, start, end):
from collections import deque
graph = self.__graph_dict
dist = {start: [start]}
q = deque((start,))
while len(q):
at = q.popleft()
for next in graph[at]:
if next not in dist:
# dist[next] = [dist[at], next]
# less efficient but nicer output
dist[next] = dist[at] + [next]
q.append(next)
return dist.get(end)
##############
def is_cyclic(self):
"""
Returns true if the graph contains a cycle, else false.
"""
# Mark all the vertices as not visited
visited = [False] * len(self)
# Call helper function to detect cycle in different DFS trees
for i, v in enumerate(visited):
# Don't recur for u if it is already visited
if v == False:
if self.dfs(i, visited, -1):
return True
return False
def dfs(self, v, visited, parent):
# Mark the current node as visited
visited[v] = True
# Recur for all the vertices adjacent to this vertex
for i in self.__graph_dict[v]:
# If the node is not visited then recurse on it
if visited[i] == False:
if(self.dfs(i, visited, v)):
return True
# If an adjacent vertex is visited and not parent of current
# vertex, then there is a cycle
elif parent != i:
return True
return False
##############
def get_random_vertex(self):
return choice(self.vertices())
@staticmethod
def random_graph(names,
connect_rate=.9,
allow_loops=True,
allow_cyclic=False,
connected=False):
vertices = set(names)
while True:
graph = Graph()
for v in vertices:
graph.add_vertex(v)
if random(1) < connect_rate:
if allow_loops:
names = list(vertices)
else:
names = list(vertices - set((v,)))
graph.add_edge({v, choice(names)})
if not connected or graph.is_connected():
if allow_cyclic or not graph.is_cyclic():
break
return graph

Wyświetl plik

@ -0,0 +1,132 @@
#*- coding: utf-8 -*-
from __future__ import division, print_function
from random import sample, choice
class Grid():
def __init__(self, graph, cols, rows, mode=0):
"""
mode 0: heavy center
mode 1: heavy perifery
"""
self.cols = cols
self.rows = rows
self.graph = graph
self.grid = self.generate_points(mode)
self.other_grid = self.generate_points(mode)
self.recalculate_d()
def generate_points(self, mode):
points = []
for i in range(self.cols * self.rows):
x = i % self.cols
y = i // self.rows
z = 0
points.append([x, y, z])
p_dist = lambda p: dist(p[0], p[1], self.cols / 2, self.rows / 2)
points = sorted(points, key=p_dist)
if mode == 0:
v_list = reversed(sorted(self.graph.vertices(),
key=self.graph.vertex_degree))
elif mode == 1:
v_list = sorted(self.graph.vertices(),
key=self.graph.vertex_degree)
else:
v_list = self.graph.vertices()
print("random mode")
return {v: p for v, p in zip(v_list, points)}
def __getitem__(self, k):
return self.grid[k]
def __setitem__(self, k, v):
self.grid[k] = v
def __len__(self):
return len(self.grid)
def keys(self):
return self.grid.keys()
def values(self):
return self.grid.values()
def __iter__(self):
return iter(self.grid)
def swap(self, num=2):
grid = self.grid
graph = self.graph
fail = 0
n = m = self.edge_distances()
while m <= n and fail < len(graph) ** 2:
new_grid = dict(grid)
if num == 2:
a, b = sample(graph.vertices(), 2)
new_grid[a], new_grid[b] = new_grid[b], new_grid[a]
else:
ks = sample(graph.vertices(), num)
vs = [grid[k] for k in sample(ks, num)]
for k, v in zip(ks, vs):
new_grid[k] = v
n = self.edge_distances(new_grid)
if m > n:
t = "{:.2%} at: {} tries of {}v shuffle/swap" \
.format((n - m) / m, fail + 1, num)
print("\n" + t, end="")
self.grid = new_grid
self.recalculate_d()
else:
fail += 1
print(".", end='')
def recalculate_d(self):
for k in self.graph.vertices():
d = self.graph.vertex_degree(k)
self.other_grid[k][2] = d
self.grid[k][2] = d
def edge_distances(self, ng=None):
grid = ng or self.grid
total = 0
for edge in self.graph.edges():
if len(edge) == 2:
a, b = edge
xa, ya, _ = grid[a]
xb, yb, _ = grid[b]
d = dist(xa, ya, xb, yb)
total += d
return total
def edges(self, t):
z = zip(self.sorted_edges(self.grid),
self.sorted_edges(self.other_grid))
lerped_edges = []
for za, zb in z:
lerped_edges.append([lerp(ea, eb, t)
for ea, eb in zip(za, zb)])
return lerped_edges
def sorted_edges(self, grid):
edgs = []
for e in self.graph.edges():
if len(e) == 2:
a, b = tuple(e)
(xa, ya, za) = grid[a]
(xb, yb, zb) = grid[b]
da = self.graph.vertex_degree(a)
db = self.graph.vertex_degree(b)
deg = ((da + db) / 2) # r / (Grid.w / 10)
edgs.append((xa, ya, xb, yb, za, zb, deg))
return sorted(edgs, key=lambda e: e[6])
def dim_grid(n):
a = int(sqrt(n))
b = n // a
if a * b < n:
b += 1
print(u'{}: {} × {} ({})'.format(n, a, b, a * b))
return a, b

Wyświetl plik

@ -0,0 +1,170 @@
from __future__ import print_function, division
import pickle
from graph import Graph
from grid import Grid, dim_grid
from arcs import var_bar
thread_running = False
viz_stat = False
selected_v = None
t, v = 0, 0.01
animate = False
def setup():
size(600, 600)
# colorMode(HSB)
textAlign(CENTER, CENTER)
f = createFont("Source Code Pro Bold", 12)
textFont(f)
global graph, margin, w, h, cols, rows
graph = Graph.random_graph(range(100),
connect_rate=.95,
allow_loops=False,
connected=True,
allow_cyclic=True)
margin = width / 40
cols, rows = dim_grid(len(graph))
w, h = (width - margin * 2) / cols, (height - margin * 2) / rows
setup_grid(cols, rows)
def setup_grid(cols, rows, mode=0):
# create a random graph and a dict of grid postions for its vertices
global grid, m
grid = Grid(graph, cols, rows, mode=mode)
m = grid.edge_distances() # "metric", sum of edge distances
print("Cyclic: " + str(graph.is_cyclic()))
global gx, gy
gx, gy = 0, 100
def draw():
global t
# scale(.5)
background(32, 0, 64)
for e in reversed(grid.edges(t)):
xa, ya, xb, yb, za, zb, deg = e
strokeWeight(map(deg, 1.5, 5, 4, 1))
fill(255, 200)
var_bar(margin + w / 2 + xa * w,
margin + h / 2 + ya * w,
margin + w / 2 + xb * w,
margin + h / 2 + yb * w,
za * w / 10, zb * w / 10)
strokeWeight(1)
circle(margin + w / 2 + xa * w,
margin + h / 2 + ya * h, 10)
circle(margin + w / 2 + xb * w,
margin + h / 2 + yb * h, 10)
for v in grid.keys():
x, y, z = grid[v]
# if v == selected_v:
# fill(0)
# elif mouse_near(v):
# fill(128)
# else:
# noFill()
# strokeWeight(1)
# circle(margin + w / 2 + x * w,
# margin + h / 2 + y * h, 10)
if viz_stat:
fill(255, 0, 0)
text("{}:{}".format(v, graph.vertex_degree(v)).upper(),
margin + w / 2 + x * w - 15,
margin + h / 2 + y * h - 5)
fill(100)
text(int(m), 30, 20)
if selected_v:
x, y, _ = grid[selected_v]
stroke(0)
strokeWeight(5)
line(margin + w / 2 + x * w,
margin + h / 2 + y * h, mouseX, mouseY)
if animate and 0 < t < 1:
t += v
def keyTyped():
global gx, gy, viz_stat, grid, graph, animate, t, v
if key == 'x':
grid.grid, grid.other_grid = grid.other_grid, grid.grid
elif key == 'a':
animate != animate
elif key == ' ':
v = -v
elif key == 'r':
setup_grid(cols, rows, mode=0)
elif key == 'e':
setup_grid(cols, rows, mode=1)
elif key == 'R':
setup_grid(cols, rows, mode=2)
elif key == 'v':
viz_stat = not viz_stat
elif key == 'p':
saveFrame("####.png")
elif key == 'd':
with open("data/grid.data", "w") as f:
pickle.dump((graph, grid), f)
print("dump grid.data")
elif key == 'l':
with open("data/grid.data", "rb") as f2:
graph, grid = pickle.load(f2)
print("load grid.data")
else:
if not thread_running:
thread("swapping")
else:
print("\nalready swapping")
def swapping():
global grid, thread_running, gx, gy, m
if str(key) not in 's23456789':
print("wrong key!")
return
thread_running = True
print("starting thread.", end="")
m = grid.edge_distances()
len_graph = len(graph)
multiple = 1 if mousePressed else 100
for _ in range(multiple):
if key == 's':
grid.swap(num=len_graph)
if str(key) in '234556789':
grid.swap(num=int(key))
n = grid.edge_distances()
gx += 1
if n < m:
gy -= gy * (m - n) / m
m = n
thread_running = False
print("\nending thread.")
def mouse_near(v):
x, y, _ = grid[v]
return dist(margin + w / 2 + x * w,
margin + h / 2 + y * h, mouseX, mouseY) < 12
def mousePressed():
global selected_v
for v in grid.keys():
if mouse_near(v):
selected_v = v
return
def mouseReleased():
global selected_v
if selected_v:
for v in grid.keys():
if mouse_near(v):
grid[selected_v], grid[v] = grid[v], grid[selected_v]
grid.recalculate_d()
selected_v = None
# TODO IDEAS:
# Find distance outliers and try to shuffle them closer
# Run many times without visualisation on Python 3 to get some huge samples