├── .gitignore ├── Makefile ├── README.md ├── main.py ├── LICENSE.md ├── render.py ├── layout.py ├── viewer.py └── layout.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.so 3 | *.png 4 | 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: _layout.so 2 | 3 | _layout.so: layout.c 4 | gcc -std=c99 -O3 -shared -o _layout.so layout.c 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## GraphLayout 2 | 3 | Graph drawing using simulated annealing for layout. 4 | 5 | ### Input 6 | 7 | path = 'output.png' 8 | size = 800 9 | edges = [ 10 | (1, 2), (1, 3), (1, 4), (2, 4), (2, 5), (3, 6), 11 | (4, 3), (4, 6), (4, 7), (5, 4), (5, 7), (7, 6), 12 | ] 13 | create_bitmap(path, size, edges) 14 | 15 | ### Output 16 | 17 | ![Screenshot](http://i.imgur.com/4TP0gRM.png) 18 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import layout 2 | import render 3 | import wx 4 | 5 | def create_bitmap(path, max_width, max_height, edges): 6 | app = wx.App(None) 7 | nodes = layout.layout(edges) 8 | bitmap = render.render(max_width, max_height, edges, nodes) 9 | bitmap.SaveFile(path, wx.BITMAP_TYPE_PNG) 10 | 11 | if __name__ == '__main__': 12 | path = 'output.png' 13 | size = 800 14 | edges = [ 15 | (1, 2), (1, 3), (1, 4), (2, 4), (2, 5), (3, 6), 16 | (4, 3), (4, 6), (4, 7), (5, 4), (5, 7), (7, 6), 17 | ] 18 | create_bitmap(path, size, size, edges) 19 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (C) 2017 Michael Fogleman 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /render.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | 3 | from math import atan2, cos, sin, pi 4 | import wx 5 | 6 | FONT = 'Helvetica' 7 | BACKGROUND = '#FFFFFF' 8 | NODE_FILL = '#FFEBD3' 9 | NODE_COLOR = '#00283F' 10 | EDGE_COLOR = '#00283F' 11 | TEXT_COLOR = '#00283F' 12 | NODE_WIDTH = 2 13 | EDGE_WIDTH = 2 14 | 15 | def render(max_width, max_height, edges, nodes): 16 | padding = 0.5 17 | positions = nodes.values() 18 | x1 = min(x for x, y in positions) - padding 19 | y1 = min(y for x, y in positions) - padding 20 | x2 = max(x for x, y in positions) + padding 21 | y2 = max(y for x, y in positions) + padding 22 | scale = min(max_width / (x2 - x1), max_height / (y2 - y1)) 23 | def sx(x): 24 | return (x - x1) * scale 25 | def sy(y): 26 | return (y - y1) * scale 27 | width, height = sx(x2), sy(y2) 28 | bitmap = wx.EmptyBitmap(width, height) 29 | dc = wx.MemoryDC(bitmap) 30 | font = dc.GetFont() 31 | font.SetFaceName(FONT) 32 | font.SetPointSize(scale / 8) 33 | dc.SetFont(font) 34 | dc.SetTextForeground(TEXT_COLOR) 35 | dc.SetBackground(wx.Brush(BACKGROUND)) 36 | dc.Clear() 37 | dc.SetBrush(wx.Brush(EDGE_COLOR)) 38 | dc.SetPen(wx.Pen(EDGE_COLOR, EDGE_WIDTH)) 39 | for a, b in edges: 40 | ax, ay = nodes[a] 41 | bx, by = nodes[b] 42 | a = atan2(by - ay, bx - ax) 43 | ax, ay = ax + cos(a) / 4, ay + sin(a) / 4 44 | bx, by = bx - cos(a) / 4, by - sin(a) / 4 45 | dc.DrawLine(sx(ax), sy(ay), sx(bx), sy(by)) 46 | b = a + pi / 2 47 | cx0, cy0 = bx - cos(a) / 12, by - sin(a) / 12 48 | cx1, cy1 = cx0 + cos(b) / 24, cy0 + sin(b) / 24 49 | cx2, cy2 = cx0 - cos(b) / 24, cy0 - sin(b) / 24 50 | points = [(bx, by), (cx1, cy1), (cx2, cy2), (bx, by)] 51 | points = [(sx(x), sy(y)) for x, y in points] 52 | dc.DrawPolygon(points) 53 | dc.SetBrush(wx.Brush(NODE_FILL)) 54 | dc.SetPen(wx.Pen(NODE_COLOR, NODE_WIDTH)) 55 | for key, (x, y) in nodes.items(): 56 | x, y = sx(x), sy(y) 57 | dc.DrawCircle(x, y, scale / 4) 58 | text = str(key) 59 | tw, th = dc.GetTextExtent(text) 60 | dc.DrawText(text, x - tw / 2, y - th / 2) 61 | return bitmap 62 | -------------------------------------------------------------------------------- /layout.py: -------------------------------------------------------------------------------- 1 | from ctypes import CDLL, CFUNCTYPE, POINTER, Structure, c_float, c_int, byref 2 | 3 | dll = CDLL('_layout.so') 4 | 5 | MAX_EDGES = 128 6 | MAX_NODES = 128 7 | 8 | WEIGHTS = { 9 | 'node_node': 100, 10 | 'node_edge': 100, 11 | 'edge_edge': 10, 12 | 'rank': 5, 13 | 'length': 1, 14 | 'area': 1, 15 | } 16 | 17 | class Node(Structure): 18 | _fields_ = [ 19 | ('rank', c_int), 20 | ('x', c_float), 21 | ('y', c_float), 22 | ] 23 | 24 | class Edge(Structure): 25 | _fields_ = [ 26 | ('a', c_int), 27 | ('b', c_int), 28 | ] 29 | 30 | class Model(Structure): 31 | _fields_ = [ 32 | ('edge_count', c_int), 33 | ('node_count', c_int), 34 | ('edges', Edge * MAX_EDGES), 35 | ('nodes', Node * MAX_NODES), 36 | ] 37 | 38 | class Attrib(Structure): 39 | _fields_ = [ 40 | ('node_node', c_float), 41 | ('node_edge', c_float), 42 | ('edge_edge', c_float), 43 | ('rank', c_float), 44 | ('length', c_float), 45 | ('area', c_float), 46 | ] 47 | 48 | CALLBACK_FUNC = CFUNCTYPE(None, POINTER(Model), c_float) 49 | 50 | dll.anneal.restype = c_float 51 | dll.anneal.argtypes = [ 52 | POINTER(Model), POINTER(Attrib), 53 | c_float, c_float, c_int, CALLBACK_FUNC] 54 | 55 | def anneal(model, weights, max_temp, min_temp, steps, callback_func): 56 | return dll.anneal( 57 | byref(model), byref(weights), 58 | max_temp, min_temp, steps, CALLBACK_FUNC(callback_func)) 59 | 60 | def cyclic(nodes, inputs): 61 | remaining = set(nodes) 62 | while remaining: 63 | count = 0 64 | for node in nodes: 65 | if node not in remaining: 66 | continue 67 | if remaining & inputs[node]: 68 | continue 69 | count += 1 70 | remaining.remove(node) 71 | if count == 0: 72 | return True 73 | return False 74 | 75 | def rank(inputs, node, memo=None): 76 | memo = memo or {} 77 | if node in memo: 78 | return memo[node] 79 | elif inputs[node]: 80 | result = min(rank(inputs, x, memo) for x in inputs[node]) + 1 81 | else: 82 | result = 1 83 | memo[node] = result 84 | return result 85 | 86 | def topographical_sort(nodes, inputs): 87 | if cyclic(nodes, inputs): 88 | ranks = [0] * len(nodes) 89 | else: 90 | memo = {} 91 | ranks = [rank(inputs, node, memo) for node in nodes] 92 | return sorted(zip(ranks, nodes)) 93 | 94 | def create_model(edges): 95 | nodes = set() 96 | for a, b in edges: 97 | nodes.add(a) 98 | nodes.add(b) 99 | nodes = sorted(nodes) 100 | inputs = dict((x, set()) for x in nodes) 101 | outputs = dict((x, set()) for x in nodes) 102 | for a, b in edges: 103 | inputs[b].add(a) 104 | outputs[a].add(b) 105 | topo = topographical_sort(nodes, inputs) 106 | ranks = dict((node, rank) for rank, node in topo) 107 | lookup = dict((node, index) for index, node in enumerate(nodes)) 108 | model = Model() 109 | model.edge_count = len(edges) 110 | model.node_count = len(nodes) 111 | for index, node in enumerate(nodes): 112 | model.nodes[index].rank = ranks[node] 113 | model.nodes[index].x = 0 114 | model.nodes[index].y = 0 115 | for index, (a, b) in enumerate(edges): 116 | model.edges[index].a = lookup[a] 117 | model.edges[index].b = lookup[b] 118 | return model, lookup 119 | 120 | def create_attrib(data): 121 | keys = [ 122 | 'node_node', 123 | 'node_edge', 124 | 'edge_edge', 125 | 'rank', 126 | 'length', 127 | 'area', 128 | ] 129 | result = Attrib() 130 | for key in keys: 131 | setattr(result, key, data.get(key, WEIGHTS[key])) 132 | return result 133 | 134 | def layout(edges, weights=None, steps=100000, listener=None): 135 | model, lookup = create_model(edges) 136 | weights = create_attrib(weights or {}) 137 | def create_result(model): 138 | nodes = {} 139 | for key, index in lookup.items(): 140 | node = model.nodes[index] 141 | nodes[key] = (node.x, node.y) 142 | return nodes 143 | def callback_func(model, energy): 144 | if listener is not None: 145 | result = create_result(model.contents) 146 | listener(result, energy) 147 | anneal(model, weights, 100, 0.01, steps, callback_func) 148 | return create_result(model) 149 | -------------------------------------------------------------------------------- /viewer.py: -------------------------------------------------------------------------------- 1 | import layout 2 | import render 3 | import wx 4 | 5 | TESTS = [ 6 | [('t', 'te'), ('t', 'ti'), ('t', 'to'), ('te', 'tea'), ('te', 'ten'), ('tea', 'team'), ('ti', 'tin'), ('tin', 'tine'), ('to', 'ton'), ('ton', 'tone')], 7 | [(5, 11), (11, 10), (11, 2), (3, 10), (3, 8), (8, 9), (11, 9), (7, 8), (7, 11)], 8 | [(1, 2), (1, 5), (2, 5), (2, 3), (3, 4), (4, 5), (4, 6)], 9 | [(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)], 10 | [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], 11 | [(0, 1), (1, 2), (2, 0)], 12 | [(1, 2), (1, 5), (1, 8), (5, 6), (2, 3), (3, 4), (4, 2), (6, 7), (6, 8), (6, 3)], 13 | [(1, 2), (1, 3), (1, 4), (2, 4), (2, 5), (3, 6), (4, 3), (4, 6), (4, 7), (5, 4), (5, 7), (7, 6)], 14 | [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 1), (1, 4), (2, 5), (3, 6)], 15 | [(1, 3), (3, 2), (2, 1), (3, 5), (4, 1), (4, 2), (4, 12), (4, 13), (5, 6), (5, 8), (6, 7), (6, 8), (6, 10), (7, 10), (8, 9), (8, 10), (9, 5), (9, 11), (10, 9), (10, 11), (10, 14), (11, 12), (11, 14), (12, 13), (13, 11), (13, 15), (14, 13), (15, 14)], 16 | [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9)], 17 | [(0, 1), (0, 3), (1, 4), (1, 2), (2, 5), (3, 4), (3, 6), (4, 5), (4, 7), (5, 8), (6, 7), (7, 8)], 18 | ] 19 | 20 | class View(wx.Panel): 21 | def __init__(self, parent): 22 | super(View, self).__init__(parent) 23 | self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) 24 | self.Bind(wx.EVT_SIZE, self.on_size) 25 | self.Bind(wx.EVT_PAINT, self.on_paint) 26 | self.Bind(wx.EVT_CHAR_HOOK, self.on_char) 27 | self.index = -1 28 | self.weights = {} 29 | self.model = None 30 | self.bitmap = None 31 | wx.CallAfter(self.next) 32 | def next(self): 33 | self.index = (self.index + 1) % len(TESTS) 34 | self.compute() 35 | def compute(self): 36 | edges = TESTS[self.index] 37 | nodes = layout.layout(edges, self.weights) 38 | self.set_model((edges, nodes)) 39 | def update(self): 40 | if self.model is None: 41 | return 42 | cw, ch = self.GetClientSize() 43 | bitmap = render.render(cw, ch, *self.model) 44 | self.set_bitmap(bitmap) 45 | def set_model(self, model): 46 | self.model = model 47 | self.update() 48 | def set_weights(self, weights): 49 | self.weights = weights 50 | self.compute() 51 | def set_bitmap(self, bitmap): 52 | self.bitmap = bitmap 53 | self.Refresh() 54 | self.Update() 55 | def on_char(self, event): 56 | event.Skip() 57 | if event.GetKeyCode() == wx.WXK_ESCAPE: 58 | self.GetParent().Close() 59 | elif event.GetKeyCode() == wx.WXK_SPACE: 60 | self.next() 61 | def on_size(self, event): 62 | event.Skip() 63 | self.update() 64 | def on_paint(self, event): 65 | dc = wx.AutoBufferedPaintDC(self) 66 | dc.SetBackground(wx.Brush(render.BACKGROUND)) 67 | dc.Clear() 68 | if self.bitmap is None: 69 | return 70 | cw, ch = self.GetClientSize() 71 | bw, bh = self.bitmap.GetSize() 72 | x = cw / 2 - bw / 2 73 | y = ch / 2 - bh / 2 74 | dc.DrawBitmap(self.bitmap, x, y) 75 | dc.DrawText(str(self.index), 10, 10) 76 | 77 | class Frame(wx.Frame): 78 | def __init__(self): 79 | super(Frame, self).__init__(None) 80 | self.create_controls(self) 81 | self.SetTitle('GraphLayout') 82 | self.SetClientSize((800, 600)) 83 | self.Center() 84 | def create_controls(self, parent): 85 | panel = wx.Panel(parent) 86 | self.view = self.create_view(panel) 87 | sidebar = self.create_sidebar(panel) 88 | sizer = wx.BoxSizer(wx.HORIZONTAL) 89 | sizer.Add(self.view, 1, wx.EXPAND) 90 | sizer.Add(sidebar, 0, wx.EXPAND | wx.ALL, 10) 91 | panel.SetSizer(sizer) 92 | return panel 93 | def create_view(self, parent): 94 | return View(parent) 95 | def create_sidebar(self, parent): 96 | names = [ 97 | 'edge_edge', 98 | 'rank', 99 | 'length', 100 | 'area', 101 | ] 102 | sizer = wx.BoxSizer(wx.VERTICAL) 103 | self.sliders = [] 104 | for name in names: 105 | value = int(layout.WEIGHTS[name] * 10) 106 | text = wx.StaticText(parent, -1, name) 107 | slider = wx.Slider(parent, -1, value, 0, 100) 108 | slider.name = name 109 | slider.Bind(wx.EVT_SCROLL_THUMBRELEASE, self.on_slider) 110 | self.sliders.append(slider) 111 | sizer.Add(text) 112 | sizer.Add(slider, 0, wx.EXPAND) 113 | sizer.AddSpacer(10) 114 | return sizer 115 | def on_slider(self, event): 116 | weights = {} 117 | for slider in self.sliders: 118 | weights[slider.name] = slider.GetValue() / 10.0 119 | self.view.set_weights(weights) 120 | 121 | def main(): 122 | app = wx.App(None) 123 | frame = Frame() 124 | frame.Show() 125 | app.MainLoop() 126 | 127 | if __name__ == '__main__': 128 | main() 129 | -------------------------------------------------------------------------------- /layout.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define MAX_EDGES 128 7 | #define MAX_NODES 128 8 | 9 | #define INF 1e9 10 | #define EPS 1e-9 11 | 12 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 13 | #define MAX(a, b) ((a) > (b) ? (a) : (b)) 14 | 15 | typedef struct { 16 | int rank; 17 | float x; 18 | float y; 19 | } Node; 20 | 21 | typedef struct { 22 | int a; 23 | int b; 24 | } Edge; 25 | 26 | typedef struct { 27 | int edge_count; 28 | int node_count; 29 | Edge edges[MAX_EDGES]; 30 | Node nodes[MAX_NODES]; 31 | } Model; 32 | 33 | typedef struct { 34 | int index; 35 | float x; 36 | float y; 37 | } Undo; 38 | 39 | typedef struct { 40 | float node_node; 41 | float node_edge; 42 | float edge_edge; 43 | float rank; 44 | float length; 45 | float area; 46 | } Attrib; 47 | 48 | typedef void (*callback_func)(Model *, float); 49 | 50 | int rand_int(int n) { 51 | int result; 52 | while (n <= (result = rand() / (RAND_MAX / n))); 53 | return result; 54 | } 55 | 56 | float rand_float() { 57 | return (float)rand() / (float)RAND_MAX; 58 | } 59 | 60 | float distance(float x0, float y0, float x1, float y1) { 61 | return hypot(x1 - x0, y1 - y0); 62 | } 63 | 64 | float dot(float x0, float y0, float x1, float y1, float x2, float y2) { 65 | return (x1 - x0) * (x2 - x1) + (y1 - y0) * (y2 - y1); 66 | } 67 | 68 | float cross(float x0, float y0, float x1, float y1, float x2, float y2) { 69 | return (x1 - x0) * (y2 - y0) - (y1 - y0) * (x2 - x0); 70 | } 71 | 72 | float segment_point_distance( 73 | float x0, float y0, float x1, float y1, float x2, float y2) 74 | { 75 | if (x0 == x1 && y0 == y1) { 76 | return distance(x0, y0, x2, y2); 77 | } 78 | float d1 = dot(x0, y0, x1, y1, x2, y2); 79 | if (d1 > 0) { 80 | return distance(x1, y1, x2, y2); 81 | } 82 | float d2 = dot(x1, y1, x0, y0, x2, y2); 83 | if (d2 > 0) { 84 | return distance(x0, y0, x2, y2); 85 | } 86 | return fabs(cross(x0, y0, x1, y1, x2, y2) / distance(x0, y0, x1, y1)); 87 | } 88 | 89 | int segments_intersect( 90 | float x0, float y0, float x1, float y1, 91 | float x2, float y2, float x3, float y3) 92 | { 93 | float p1 = x1 - x0; 94 | float q1 = y1 - y0; 95 | float p2 = x3 - x2; 96 | float q2 = y3 - y2; 97 | float det = p1 * q2 - p2 * q1; 98 | if (det == 0) { 99 | return 0; 100 | } 101 | float s = (p1 * (y0 - y2) - q1 * (x0 - x2)) / det; 102 | float t = (p2 * (y0 - y2) - q2 * (x0 - x2)) / det; 103 | return s > 0 && s < 1 && t > 0 && t < 1; 104 | } 105 | 106 | void analyze(Model *model, Attrib* attrib) { 107 | // count intersecting nodes 108 | int node_node = 0; 109 | for (int i = 0; i < model->node_count; i++) { 110 | for (int j = i + 1; j < model->node_count; j++) { 111 | Node *a = &model->nodes[i]; 112 | Node *b = &model->nodes[j]; 113 | if (distance(a->x, a->y, b->x, b->y) < 1) { 114 | node_node++; 115 | } 116 | } 117 | } 118 | // count nodes on edges 119 | int node_edge = 0; 120 | for (int i = 0; i < model->edge_count; i++) { 121 | Edge *edge = &model->edges[i]; 122 | Node *a = &model->nodes[edge->a]; 123 | Node *b = &model->nodes[edge->b]; 124 | for (int j = 0; j < model->node_count; j++) { 125 | Node *c = &model->nodes[j]; 126 | if (c == a || c == b) { 127 | continue; 128 | } 129 | if (segment_point_distance( 130 | a->x, a->y, b->x, b->y, c->x, c->y) < 0.25) 131 | { 132 | node_edge++; 133 | } 134 | } 135 | } 136 | // count intersecting edges 137 | int edge_edge = 0; 138 | for (int i = 0; i < model->edge_count; i++) { 139 | for (int j = i + 1; j < model->edge_count; j++) { 140 | Edge *p = &model->edges[i]; 141 | Edge *q = &model->edges[j]; 142 | Node *a = &model->nodes[p->a]; 143 | Node *b = &model->nodes[p->b]; 144 | Node *c = &model->nodes[q->a]; 145 | Node *d = &model->nodes[q->b]; 146 | if (segments_intersect( 147 | a->x, a->y, b->x, b->y, c->x, c->y, d->x, d->y)) 148 | { 149 | edge_edge++; 150 | } 151 | } 152 | } 153 | // check node ranks 154 | int rank = 0; 155 | for (int i = 0; i < model->node_count; i++) { 156 | Node *a = &model->nodes[i]; 157 | if (a->rank == 0) { 158 | continue; 159 | } 160 | for (int j = i + 1; j < model->node_count; j++) { 161 | Node *b = &model->nodes[j]; 162 | if (a->rank >= b->rank && a->y < b->y) { 163 | rank++; 164 | } 165 | if (a->rank <= b->rank && a->y > b->y) { 166 | rank++; 167 | } 168 | } 169 | } 170 | // sum edge lengths 171 | float length = 0; 172 | for (int i = 0; i < model->edge_count; i++) { 173 | Edge *edge = &model->edges[i]; 174 | Node *a = &model->nodes[edge->a]; 175 | Node *b = &model->nodes[edge->b]; 176 | length += distance(a->x, a->y, b->x, b->y); 177 | } 178 | // compute graph area 179 | Node *node = &model->nodes[0]; 180 | float minx = node->x; 181 | float miny = node->y; 182 | float maxx = node->x; 183 | float maxy = node->y; 184 | for (int i = 1; i < model->node_count; i++) { 185 | Node *node = &model->nodes[i]; 186 | minx = MIN(minx, node->x); 187 | miny = MIN(miny, node->y); 188 | maxx = MAX(maxx, node->x); 189 | maxy = MAX(maxy, node->y); 190 | } 191 | float area = (maxx - minx) * (maxy - miny); 192 | // result 193 | attrib->node_node = node_node; 194 | attrib->node_edge = node_edge; 195 | attrib->edge_edge = edge_edge; 196 | attrib->rank = rank; 197 | attrib->length = length; 198 | attrib->area = area; 199 | } 200 | 201 | void print_attrib(Model *model) { 202 | Attrib attrib; 203 | analyze(model, &attrib); 204 | printf("node_node: %g\n", attrib.node_node); 205 | printf("node_edge: %g\n", attrib.node_edge); 206 | printf("edge_edge: %g\n", attrib.edge_edge); 207 | printf("rank: %g\n", attrib.rank); 208 | printf("length: %g\n", attrib.length); 209 | printf("area: %g\n", attrib.area); 210 | printf("\n"); 211 | } 212 | 213 | float energy(Model *model, Attrib *weights) { 214 | Attrib _attrib; 215 | Attrib *attrib = &_attrib; 216 | analyze(model, attrib); 217 | float result = 0; 218 | result += attrib->node_node * weights->node_node; 219 | result += attrib->node_edge * weights->node_edge; 220 | result += attrib->edge_edge * weights->edge_edge; 221 | result += attrib->rank * weights->rank; 222 | result += attrib->length * weights->length; 223 | result += attrib->area * weights->area; 224 | return result; 225 | } 226 | 227 | void do_move(Model *model, Undo *undo) { 228 | int index = rand_int(model->node_count); 229 | Node *node = &model->nodes[index]; 230 | undo->index = index; 231 | undo->x = node->x; 232 | undo->y = node->y; 233 | node->x = rand_int(10) / 2.0; 234 | node->y = rand_int(10) / 2.0; 235 | // float dx, dy; 236 | // do { 237 | // dx = (rand_int(3) - 1) / 2.0; 238 | // dy = (rand_int(3) - 1) / 2.0; 239 | // } while (dx == 0 && dy == 0); 240 | // node->x += dx; 241 | // node->y += dy; 242 | } 243 | 244 | void undo_move(Model *model, Undo *undo) { 245 | Node *node = &model->nodes[undo->index]; 246 | node->x = undo->x; 247 | node->y = undo->y; 248 | } 249 | 250 | void copy(Model *dst, Model *src) { 251 | dst->edge_count = src->edge_count; 252 | dst->node_count = src->node_count; 253 | memcpy(dst->edges, src->edges, sizeof(Edge) * src->edge_count); 254 | memcpy(dst->nodes, src->nodes, sizeof(Node) * src->node_count); 255 | } 256 | 257 | void randomize(Model *model) { 258 | int size = ceilf(sqrtf(model->node_count)); 259 | for (int i = 0; i < model->node_count; i++) { 260 | Node *node = &model->nodes[i]; 261 | node->x = rand_int(size); 262 | node->y = rand_int(size); 263 | } 264 | } 265 | 266 | void random_start(Model *model, Attrib *weights, int steps) { 267 | Model best; 268 | float current_energy = energy(model, weights); 269 | float best_energy = current_energy; 270 | copy(&best, model); 271 | for (int step = 0; step < steps; step++) { 272 | randomize(model); 273 | current_energy = energy(model, weights); 274 | if (current_energy < best_energy) { 275 | best_energy = current_energy; 276 | copy(&best, model); 277 | } 278 | } 279 | copy(model, &best); 280 | } 281 | 282 | float anneal( 283 | Model *model, Attrib *weights, float max_temp, float min_temp, 284 | int steps, callback_func func) 285 | { 286 | Model best; 287 | Undo undo; 288 | srand(0); 289 | random_start(model, weights, 1000); 290 | float factor = -log(max_temp / min_temp); 291 | float current_energy = energy(model, weights); 292 | float previous_energy = current_energy; 293 | float best_energy = current_energy; 294 | copy(&best, model); 295 | func(&best, best_energy); 296 | for (int step = 0; step < steps; step++) { 297 | float temp = max_temp * exp(factor * step / steps); 298 | do_move(model, &undo); 299 | current_energy = energy(model, weights); 300 | float change = current_energy - previous_energy; 301 | if (change > 0 && exp(-change / temp) < rand_float()) { 302 | undo_move(model, &undo); 303 | } 304 | else { 305 | previous_energy = current_energy; 306 | if (current_energy < best_energy) { 307 | best_energy = current_energy; 308 | copy(&best, model); 309 | func(&best, best_energy); 310 | if (current_energy <= 0) { 311 | break; 312 | } 313 | } 314 | } 315 | } 316 | copy(model, &best); 317 | print_attrib(model); 318 | return best_energy; 319 | } 320 | --------------------------------------------------------------------------------