├── img └── .EMPTY ├── profile └── .EMPTY ├── speedup └── __init__.py ├── .gitignore ├── Makefile ├── setup.py ├── README.md ├── LICENSE ├── main_ani.py ├── orbitals.py ├── main_ani_detail.py ├── main.py ├── src └── speedup.pyx └── render.py /img/.EMPTY: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /profile/.EMPTY: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /speedup/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.png 2 | *.*~ 3 | *.swp 4 | run*/* 5 | *.pyc 6 | .ropeproject 7 | build 8 | profile 9 | *.so 10 | *.c 11 | *.html 12 | 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | all: 4 | python setup.py build_ext --inplace 5 | cython -a src/*.pyx 6 | mv *.so speedup 7 | 8 | html: 9 | cython -a src/*.pyx 10 | 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from distutils.core import setup 4 | from distutils.extension import Extension 5 | from Cython.Build import cythonize 6 | from Cython.Distutils import build_ext 7 | import numpy 8 | 9 | _extra = ['-O3', '-ffast-math'] 10 | 11 | extensions = [ 12 | Extension('speedup', 13 | sources = ['./src/speedup.pyx'], 14 | extra_compile_args = _extra 15 | ) 16 | ] 17 | 18 | setup( 19 | name = "speedup", 20 | cmdclass={'build_ext' : build_ext}, 21 | include_dirs = [numpy.get_include()], 22 | ext_modules = cythonize(extensions,include_path = [numpy.get_include()]) 23 | ) 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Orbitals 2 | ============= 3 | 4 | Orbitals is based on the naive optimization process that Jared Tarbell has 5 | implemented in his "[Happy 6 | Place](http://www.complexification.net/gallery/machines/happyPlace/index.php)" 7 | algorithm. Nodes are created in some, more or less structured arangement, 8 | before they start to make friends with their neighbors. Gradually the nodes 9 | make an effort to get closer to their friends, whilst at the same time keeping 10 | a comfortable distance from the nodes they have not befriended. The 11 | visualization is made by drawing lines betweeen pairs of friends as they walk 12 | around the canvas. Thus the final image is a history of all the friendships. 13 | 14 | This is the fastest version. Probably. 15 | 16 | ![orbitals](https://inconvergent.net/img/orbitals_ba.jpg "orbitals") 17 | 18 | ----------- 19 | https://inconvergent.net 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Anders Hoff 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /main_ani.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import gtk 5 | 6 | #np.random.seed(1) 7 | 8 | COLOR_PATH = '../colors/dark_cyan_white_black.gif' 9 | #COLOR_PATH = '../colors/shimmering.gif' 10 | 11 | SIZE = 1080 # size of png image 12 | NUM = 200 # number of nodes 13 | MAXFS = 10 # max friendships pr node 14 | 15 | BACK = [1,1,1,1] # background color 16 | GRAINS = 10 17 | ALPHA = 0.05 # opacity of drawn points 18 | 19 | STP = 0.0001 20 | 21 | RAD = 0.26 # radius of starting circle 22 | FARL = 0.13 # ignore "enemies" beyond this radius 23 | NEARL = 0.02 # do not attempt to approach friends close than this 24 | 25 | UPDATE_NUM = 35 26 | 27 | FRIENDSHIP_RATIO = 0.1 # probability of friendship dens 28 | FRIENDSHIP_INITIATE_PROB = 0.1 # probability of friendship initation attempt 29 | 30 | print 31 | print 'SIZE', SIZE 32 | print 'NUM', NUM 33 | print 'STP', STP 34 | print 35 | print 'MAXFS', MAXFS 36 | print 'GRAINS', GRAINS 37 | print 'COLOR_PATH', COLOR_PATH 38 | print 'RAD', RAD 39 | print 'FRIENDSHIP_RATIO', FRIENDSHIP_RATIO 40 | print 'FRIENDSHIP_INITIATE_PROB', FRIENDSHIP_INITIATE_PROB 41 | print 42 | 43 | 44 | def main(): 45 | 46 | #from time import time 47 | from render import Animate 48 | from orbitals import Orbitals 49 | from time import time 50 | 51 | 52 | orbitals = Orbitals(NUM,STP,FARL,NEARL,FRIENDSHIP_RATIO, 53 | FRIENDSHIP_INITIATE_PROB,MAXFS) 54 | orbitals.init(RAD) 55 | 56 | def step_wrap(render): 57 | 58 | t1 = time() 59 | for i in xrange(UPDATE_NUM): 60 | orbitals.step() 61 | render.connections(*orbitals.get_render_data()) 62 | t2 = time() 63 | 64 | print render.steps, t2-t1 65 | 66 | return True 67 | 68 | render = Animate(COLOR_PATH,BACK,ALPHA,GRAINS,SIZE, step_wrap) 69 | 70 | gtk.main() 71 | 72 | 73 | if __name__ == '__main__' : 74 | 75 | if False: 76 | 77 | import pyximport 78 | pyximport.install() 79 | import pstats, cProfile 80 | 81 | fn = './profile/profile' 82 | cProfile.runctx("main()", globals(), locals(), fn) 83 | p = pstats.Stats(fn) 84 | p.strip_dirs().sort_stats('cumulative').print_stats() 85 | 86 | else: 87 | 88 | main() 89 | 90 | -------------------------------------------------------------------------------- /orbitals.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from numpy.random import random, randint 5 | from numpy import zeros, sin, cos 6 | 7 | class Orbitals(object): 8 | 9 | def __init__(self,num,stp,farl,nearl,friendship_ratio, 10 | friendship_initiate_prob,maxfs): 11 | 12 | self.num = num 13 | self.stp = stp 14 | self.farl = farl 15 | self.nearl = nearl 16 | self.friendship_ratio = friendship_ratio 17 | self.friendship_initiate_prob = friendship_initiate_prob 18 | self.maxfs = maxfs 19 | 20 | self.X = zeros(num,'float') 21 | self.Y = zeros(num,'float') 22 | self.R = zeros((num,num),'float') 23 | self.A = zeros((num,num),'float') 24 | self.F = zeros((num,num),'int') 25 | 26 | def make_friends(self,i): 27 | 28 | cand_num = self.F.sum(axis=1) 29 | 30 | maxfs = self.maxfs 31 | F = self.F 32 | 33 | if cand_num[i]>=maxfs: 34 | return 35 | 36 | cand_mask = cand_num0: 149 | if d= indsy 97 | 98 | for i,j in zip(indsx[mask],indsy[mask]): 99 | move_to(X[i],Y[i]) 100 | line_to(X[j],Y[j]) 101 | stroke() 102 | 103 | def connections(self,X,Y,F,A,R): 104 | 105 | from numpy.random import random 106 | from numpy import sin, cos 107 | 108 | num = len(X) 109 | one = self.one 110 | 111 | rectangle = self.ctx.rectangle 112 | fill = self.ctx.fill 113 | set_source_rgba = self.ctx.set_source_rgba 114 | 115 | colors = self.colors 116 | n_colors = self.n_colors 117 | alpha = self.alpha 118 | grains = self.grains 119 | 120 | indsx,indsy = F.nonzero() 121 | mask = indsx >= indsy 122 | 123 | for i,j in zip(indsx[mask],indsy[mask]): 124 | a = A[i,j] 125 | d = R[i,j] 126 | scales = random(grains)*d 127 | xp = X[i] - scales*cos(a) 128 | yp = Y[i] - scales*sin(a) 129 | 130 | r,g,b = colors[ (i*num+j) % n_colors ] 131 | set_source_rgba(r,g,b,alpha) 132 | 133 | for x,y in zip(xp,yp): 134 | rectangle(x,y,one,one) 135 | fill() 136 | 137 | class Animate(Render): 138 | 139 | def __init__(self,color_path,back,alpha,grains,size, step): 140 | 141 | Render.__init__(self, color_path, back,alpha, grains, size) 142 | 143 | window = gtk.Window() 144 | window.resize(self.size, self.size) 145 | 146 | self.step = step 147 | 148 | window.connect("destroy", self.__destroy) 149 | darea = gtk.DrawingArea() 150 | darea.connect("expose-event", self.expose) 151 | window.add(darea) 152 | window.show_all() 153 | 154 | self.darea = darea 155 | self.steps = 0 156 | 157 | gobject.idle_add(self.step_wrap) 158 | 159 | def __destroy(self,*args): 160 | 161 | gtk.main_quit(*args) 162 | 163 | def expose(self,*args): 164 | 165 | cr = self.darea.window.cairo_create() 166 | cr.set_source_surface(self.sur,0,0) 167 | cr.paint() 168 | 169 | def step_wrap(self): 170 | 171 | res = self.step(self) 172 | self.steps += 1 173 | self.expose() 174 | 175 | return res 176 | 177 | --------------------------------------------------------------------------------