Skip to content

Commit

Permalink
Initial work on ratemycurve
Browse files Browse the repository at this point in the history
  • Loading branch information
Nick Johnson committed May 29, 2012
1 parent 3d9c2b5 commit b1d7af8
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 11 deletions.
15 changes: 14 additions & 1 deletion python/appengine/app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ threadsafe: true
handlers:
- url: /random
script: randomcurve.app
- url: /.*
script: main.app

builtins:
- remote_api: on
- remote_api: on
- deferred: on

libraries:
- name: PIL
version: "1.1.7"
- name: jinja2
version: "2.6"
- name: webapp2
version: "2.3"
- name: webob
version: "1.1.1"
60 changes: 60 additions & 0 deletions python/appengine/ga.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import inspect
import random

import piclang

ATOM_MUTATION_RATE = 1.0 # Atom mutations per organism
OP_MUTATION_RATE = 0.5 # Operator mutations per organism
CHANGE_TYPE_PROBABILITY = 0.1 # Chance an atom will change type

def cut_and_splice(g1, g2):
p1 = random.randrange(len(g1))
p2 = random.randrange(len(g2))
return g1[:p1] + g2[p2:], g2[:p2] + g1[p1:]

def mutate(genome):
atom_probability = ATOM_MUTATION_RATE / len(genome)
op_probability = OP_MUTATION_RATE / len(genome)
for i in range(len(genome)):
if isinstance(genome[i], (int, float, tuple, piclang.PlatonicCircle, piclang.PlatonicLine)):
if random.random() < atom_probability:
genome[i] = mutate_atom(genome[i])
else:
if random.random() < op_probability:
genome[i] = mutate_op(genome[i])
return genome

def mutate_atom(instruction):
if random.random() < CHANGE_TYPE_PROBABILITY:
return random_atom(instruction)
if isinstance(instruction, (int, float)):
return mutate_number(instruction)
if isinstance(instruction, tuple):
if random.random < 0.5:
return (instruction[0], mutate_number(instruction[1]))
else:
return (mutate_number(instruction[0]), instruction[1])
atom_curves = (piclang.PlatonicCircle, piclang.PlatonicLine)
if isinstance(instruction, atom_curves):
return random.choice(atom_curves)()
return instruction

def random_atom(old_atom=None):
typ = random.choice([float, tuple, piclang.PlatonicCircle, piclang.PlatonicLine])
if typ == float:
return random.random()
if typ == tuple:
return (random.random(), random.random())
return typ()

def mutate_number(num):
if random.random() < 0.5:
# Smaller
return num * (1 - random.random() * 0.5)
else:
# Bigger
return num * (1 + random.random())

def mutate_op(instruction):
ops = [piclang.translate, piclang.scale, piclang.rotate, piclang.reverse, piclang.concat, piclang.repeat, piclang.step]
return random.choice(ops)
24 changes: 24 additions & 0 deletions python/appengine/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import webapp2
from google.appengine.ext import ndb
from webapp2_extras import jinja2

import model

class BaseHandler(webapp2.RequestHandler):
@webapp2.cached_property
def jinja2(self):
return jinja2.get_jinja2(app=self.app)

def render_template(self, filename, **template_args):
self.response.write(self.jinja2.render_template(filename, **template_args))


class IndividualHandler(BaseHandler):
def get(self, id):
individual = model.Individual.get_by_id(int(id))
self.render_template('individual.html', individual=individual)


app = webapp2.WSGIApplication([
(r'/individual/(\d+)', IndividualHandler),
])
35 changes: 35 additions & 0 deletions python/appengine/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from google.appengine.api import files
from google.appengine.api import images
from google.appengine.ext import ndb

import piclang

class Individual(ndb.Model):
genome = ndb.PickleProperty(compressed=True)
generation = ndb.IntegerProperty(required=True)
parents = ndb.KeyProperty(kind='Individual', repeated=True)
fitness = ndb.IntegerProperty(default=1500)
matches = ndb.IntegerProperty(default=0)
image = ndb.BlobKeyProperty(required=True)

@classmethod
def create(cls, genome, generation, parents):
fun = piclang.stackparse(genome)
image = piclang.render(fun, points=8192)
filename = files.blobstore.create(mime_type='image/png')
with files.open(filename, 'a') as f:
image.save(f, "PNG")
files.finalize(filename)
blob_key = files.blobstore.get_blob_key(filename)
individual = cls(
genome=genome,
generation=generation,
parents=parents,
image=blob_key
)
individual.put()
return individual

@property
def image_url(self):
return images.get_serving_url(self.image)
1 change: 1 addition & 0 deletions python/appengine/piclang.py
9 changes: 9 additions & 0 deletions python/appengine/templates/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<html>
<head>
<title>{% block title %}Rate My Curve{% endblock %}</title>
{% block head %}{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>
16 changes: 16 additions & 0 deletions python/appengine/templates/individual.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block title %}Individual #{{individual.key.id()}}{% endblock %}
{% block body %}
<h2>Individual #{{individual.key.id()}}</h2>
<img src="{{individual.image_url}}" />
<table>
<tr><th>Generation</th><td>{{individual.generation}}</td></tr>
<tr><th>Parents</th><td>
{% for parent_key in individual.parents %}
<a href="/individual/{{parent_key.id()}}">#{{parent_key.id()}}</a>
{% endfor %}
</td></tr>
<tr><th>Matches</th><td>{{individual.matches}}</td></tr>
<tr><th>Score</th><td>{{individual.fitness}}</td></tr>
</table>
{% endblock %}
31 changes: 21 additions & 10 deletions python/piclang.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,10 @@
import inspect
import math
import numbers
import readline
try:
import readline
except ImportError:
pass
try:
from PIL import Image, ImageDraw
except ImportError:
Expand Down Expand Up @@ -302,16 +305,24 @@ def __repr__(self):
l = "(%s)" % l
return "%s // %r" % (l, self.steps)

@FunctionCurve
def circle(t):
theta = 2 * math.pi * t
return (math.sin(theta), math.cos(theta))
class PlatonicCircle(Curve):
def __call__(self, t):
theta = 2 * math.pi * t
return (math.sin(theta), math.cos(theta))

def __repr__(self):
return "circle"

circle = PlatonicCircle()

class PlatonicLine(Curve):
def __call__(self, t):
return (t, t)

@FunctionCurve
def line(t):
return (t, t)
def __repr__(self):
return "line"

line = PlatonicLine()

def boustro(func, times):
return repeat(concat(func, reverse(func)), times * 0.5)
Expand Down Expand Up @@ -345,7 +356,7 @@ def render(f, points=1000, size=800, penwidth=6, gapwidth=6, bgcolor=(0, 0, 0),
for src, dest in zip(point_list, point_list[1:]):
draw.line((src, dest), fill=bgcolor, width=penwidth+gapwidth*2)
draw.line((src, dest), fill=fgcolor, width=penwidth)
im.show()
return im

class PicStack(object):
def __init__(self):
Expand Down Expand Up @@ -377,7 +388,7 @@ def stackparse(expr):
"""Parses a stack-based representation of a curve expression, returning the expression tree."""
stack = PicStack()
for token in expr:
if token is circle or token is line:
if isinstance(token, (PlatonicCircle, PlatonicLine)):
stack.push(token)
elif isinstance(token, (int, float, tuple)):
stack.push(token)
Expand Down

0 comments on commit b1d7af8

Please sign in to comment.