+ All Categories
Home > Software > Pybelsberg — Constraint-based Programming in Python

Pybelsberg — Constraint-based Programming in Python

Date post: 10-Feb-2017
Category:
Upload: christoph-matthies
View: 280 times
Download: 2 times
Share this document with a friend
51
Conrad Calmez Christoph Matthies Robert Lehmann Pybelsberg © 2006 by Jeremy Quinn, https://www.flickr.com/photos/sharkbait/314820030 1
Transcript

Conrad Calmez Christoph Matthies

Robert Lehmann

Pybelsberg

© 2006 by Jeremy Quinn, https://www.flickr.com/photos/sharkbait/3148200301

a = pt(100, 100)b = pt(200, 200)

always {a.dist(b) == 200

}

a.setX(50)a.dist(b) # => 200

babelsberg-js

© 2006 by Jeremy Quinn, https://www.flickr.com/photos/sharkbait/314820030

https://github.com/timfel/babelsberg-js2

a = pt(100, 100)b = pt(200, 200)

always {a.dist(b) == 200

}

a.setX(50)a.dist(b) # => 200

© 2006 by Jeremy Quinn, https://www.flickr.com/photos/sharkbait/314820030

babelsberg-js

3

a = Point(100, 100)b = Point(200, 200)

@alwaysdef constraint(): return a.dist(b) == 200

a.x = 50a.dist(b) # => 200

pybelsberg

© 2006 by Jeremy Quinn, https://www.flickr.com/photos/sharkbait/3148200304

100

200

100

200

141.42

a

b

5

p = Problem()

p.a_x = p.a_y = 100; p.b_x = p.b_y = 200

constraint = …

p.always(constraint)

print(dist((p.a_x, p.a_y), (p.b_x, p.b_y)))# => 200

Boilerplate

6

p = Problem()

p.a_x = p.a_y = 100; p.b_x = p.b_y = 200

constraint = …

p.always(constraint)

print(dist((p.a_x, p.a_y), (p.b_x, p.b_y)))# => 200

Boilerplate

?

7

100

200

100

200

a.x

f(a.x) = √(a.x−b.x)² + (a.y−b.y)²

8

a

b

100

200

100

200

a.x

f(a.x) = √(a.x−b.x)² + (a.y−b.y)²f(a.x) = 200

9

a

b

100

200

100

200

a.x

f(a.x) = √(a.x−b.x)² + (a.y−b.y)²f(a.x) = 200

f

10

a

b

100

200

100

200

a.x

f(a.x) = √(a.x−b.x)² + (a.y−b.y)²f(a.x) = 200

g(a.x) = f(a.x) − 200 = 0

11

a

b

100

200

100

200

a.x

200

26.7

9

12

a

b

def constraint(a_x, a_y, b_x, b_y):

return dist((a_x, a_y), (b_x, b_y)) — 200

1Defining the constraint

actually: f(a.x, a.y, b.x, b.y) = √(a.x−b.x)² + (a.y−b.y)²

13

Satisfying constraints

● Finding values to satisfy constraints is solving equations.

● Solving equations means finding theroot of polynomials.

dist((a_x, a_y), (b_x, b_y)) — 200)

14

def constraint(a_x, a_y, b_x, b_y):return dist((a_x, a_y), (b_x, b_y)) — 200

def constraint(a_x, a_y, b_x, b_y):return dist((a_x, a_y), (b_x, b_y)) == 200

2Define constraints naturally

15

class Expr(object):

def __add__(self, other):

return Expr('+', self, other)

def __eq__(self, other):

return Expr('=', self, other)

def to_eval(self): return self.left.to_eval() + self.operator \ + self.right.to_eval()

Remember performed operations

16

def constraint(a_x, a_y, b_x, b_y):return dist((a_x, a_y), (b_x, b_y)) == 200

3No explicit declaration of parameters

17

def foo(): x

foo() # => NameError: global name 'x' is not defined

Variables in functions

18

class Namespace(dict): def __getattr__(self, key): print("getattr", key)

def foo(): a + b

ns = Namespace()proxy = types.FunctionType(foo.__code__, ns)proxy()# => ???

Execute function in different global scope

19

Execute function in different global scope

class Namespace(dict): def __getattr__(self, key): print("getattr", key)

def foo(): a + b

ns = Namespace()proxy = types.FunctionType(foo.__code__, ns)proxy()# => getattr a# => getattr b

20

2.7.6case LOAD_GLOBAL:

w = GETITEM(names, oparg); x = PyDict_GetItem(f->f_globals, w); PUSH(x);

3.3.5TARGET(LOAD_GLOBAL)

w = GETITEM(names, oparg); if (PyDict_CheckExact(f->f_globals)) {

… } else {

/* Slow-path if globals or builtins is not a dict */ x = PyObject_GetItem(f->f_globals, w);

… }

PUSH(x);

CPython: Python/ceval.c

21

def constraint():return dist((a_x, a_y), (b_x, b_y)) == 200

a_x = 50print(a_x, a_y, b_x, b_y)# => 50 100 200 -32.14print(dist(a_x, a_y), (b_x, b_y)))# => 200

4Work with global variables

22

TARGET(STORE_FAST) v = POP(); // SETLOCAL(oparg, v);PyObject *tmp = fastlocals[i];fastlocals[i] = value;Py_XDECREF(tmp);FAST_DISPATCH();

CPython: Python/ceval.c

no Python protocol is used -- GAME OVER23

class Namespace(dict): def __setattr__(self, key): print("setattr", key)

def foo(): global a a = 2

ns = Namespace()proxy = types.FunctionType(foo.__code__, ns)proxy()# => no output ☹

Function globals can be overridden?

24

TARGET(STORE_GLOBAL) { PyObject *name = GETITEM(names, oparg); PyObject *v = POP(); int err; err = PyDict_SetItem(f->f_globals, name, v); Py_DECREF(v); if (err != 0) goto error; DISPATCH(); }

GAME OVER -- bug, see ticket #1402289

CPython: Python/ceval.c

25

class in_constrained(constraint):a_x = 50

Use class as scope

26

class Namespace(dict):def __setitem__(self, key, val):

print("setitem", key, val)

class Meta(type):def __prepare__(self, obj):

return Namespace()

class Object(metaclass=Meta):x = 2

# => setitem __module__ __main__# => setitem __qualname__ Object# => setitem x 2

Use class as scope

27

class Namespace(dict):def __setitem__(self, key, val):

print("setitem", key, val)

class Meta(type):def __prepare__(self, obj):

return Namespace()

class Object(metaclass=Meta):x = 2

# => setitem __module__ __main__# => setitem __qualname__ Object# => setitem x 2

Use class as scope

Stamp © 2012 Stuart Miles, FreeDigitalPhotos.net, http://www.freedigitalphotos.net/images/Other_Metaphors_and__g307-Reject_Stamp_p86053.html28

a = Point(100, 100)b = Point(200, 200)

@alwaysdef point_distance_constraint(): return a.dist(b) == 200

a.x = 50# a.__setattr__('x', 50) ~› solve()a.dist(b) # => 200

5Catch instance variables

29

class A(object): pass

def setattr(self, name, value): print("setattr", name, value)

a = A()

a.__setattr__ = setattr

a.x = 10# no output ☹

Notice instance variable assignment

30

3.3.5int PyObject_SetAttr(PyObject *v, PyObject *name, PyObject *value){

PyTypeObject *tp = Py_TYPE(v);int err;Py_INCREF(name);

if (tp->tp_setattr != NULL) {err = (*tp->tp_setattr)(v, name_str, value);Py_DECREF(name);return err;

}…return -1;

}

CPython: Objects/object.c

31

class A(object): pass

def setattr(self, name, value): print("setattr", name, value)

a = A()

type(a).__setattr__ = setattr

a.x = 10# => setattr x 10

Use type

32

always: a.dist(b) == 200

6Source code transforms

33

import codecsdef decode(text): return u'x=5\n' + text.decode('utf8')codecs.register('babelsberg', decode)

# encoding: babelsbergprint(x) # => 5

Custom encoding

*

* not the actual codecs API 34

## main.py ☹import custom_codecimport demo

## demo.py# encoding: babelsbergprint(x) # => 5

Custom encoding, caveat

depends on

35

## main.py ☹import custom_codecimport demo

## demo.py# encoding: babelsbergprint(x) # => 5

Custom encoding, caveat

36

<demo>a = Point(100.0, 100.0)b = Point(200.0, 200.0)

@alwaysdef constant_distance(): yield a.distance(b) == 200

assert_almost_equals(a.distance(b), 200)

a.x = 50assert_almost_equals(a.distance(b), 200)

37

0 LOAD_GLOBAL 0 (a) 3 LOAD_ATTR 1 (dist) 6 LOAD_GLOBAL 2 (b) 9 CALL_FUNCTION 112 LOAD_CONST 1 (200)15 COMPARE_OP 2 (==)18 POP_TOP 19 LOAD_CONST 0 (None)22 RETURN_VALUE

a = Point(100, 100)b = Point(200, 200)

@alwaysdef constant_distance(): a.dist(b) == 200

patch(obj):for each instance variable of obj:

remember original valuereplace with wrapped valuea.x, a.y, b.x, b.y

38

constraint = constant_distance()

for all remembered instance variables:solver.add(instance_variable == value)

solver.add(constraint)solver.solve()

(= 200 (sqrt(+

(**(- a.x b.x)2)

(**(- a.y b.y)2)

)))

39

pybelsberg.py

Python

Theorem Solver?40

SciPy

41

scipy.optimize.fsolve(

func, #A function that takes at least one argument.

x0, #The starting estimate for the roots of func(x) = 0.

args=(), #Any extra arguments to func.

fprime=None, #A function to compute the Jacobian of func with derivatives across the rows.

By default, the Jacobian will be estimated.

full_output=0, #If True, return optional outputs.

col_deriv=0, #Specify whether the Jacobian function computes derivatives down the columns

(faster, because there is no transpose operation).

xtol=1.49012e-08, #The calculation will terminate if the relative error between two

consecutive iterates is at most xtol

maxfev=0, #The maximum number of calls to the function. If zero, then 100*(N+1) is the maximum where N is the number of elements in x0.

band=None, #If set to a two-sequence containing the number of sub- and super-diagonals within

the band of the Jacobi matrix, the Jacobi matrix is considered banded (only for fprime=None).

epsfcn=None, #A suitable step length for the forward-difference approximation of the

Jacobian (for fprime=None). If epsfcn is less than the machine precision, it is assumed that the relative

errors in the functions are of the order of the machine precision.

factor=100,

diag=None)

42

scipy.optimize.fsolve

constraint = lambda args: [ math.sqrt( (args[0]-args[1])**2 + (args[2]-args[3])**2 ) - 200 ]*4

fsolve(constraint, [1, 1, 1, 1])# => (array([ 201., 1., 1., 1.])

starting values

43

scipy.optimize.fsolve

● Requires transformation○ “Return the roots of the (non-linear) equations defined by

func(x) = 0”○ Cannot access instance variables○ Function value must be closer to 0 if the parameters are

closer to the solution○ x = y → x - y = 0 x > y → x - y if x - y > 0 else 0

● Requires starting estimate○ Hard to determine best value for user-defined equations

44

Z345

Z3 theorem prover

● Developed by Microsoft Research http://z3.codeplex.com/

● General-purpose solver (not only for finding roots)● Many built-in theories

○ linear arithmetic, nonlinear arithmetic, bitvectors, arrays, datatypes, quantifiers

● Python bindings○ already does ASTs

46

from z3 import *

a_x, a_y = Real('a.x'), Real('a.y')b_x, b_y = Real('b.x'), Real('b.y')

s = Solver()s.add(a_x == 100, …)s.add(sqrt((a_x-b_x)**2 + (a_y-b_y)**2) == 200)

print(s.check()) # => satprint(s.model()) # => [a.x = 26.79, a.y = 100, …]

Z3 theorem prover

47

Z3 theorem prover

● Quality of life improvement○ Try to minimize the amount of variables that are

changed by the solver due to a constraint

48

Add all constraints● Put a rollback point (“push” in Z3 lingo)● Add all current variables (eg. from Point constructor, example

a = Point(50, 100)) as “soft constraints”○ ie., boolnew → a.x = 50

● Solver tells us which implications are wrong (wereinvalidated to satisfy constraints)

● We remove these from future runs, so that these variables are preferably modified

Z3 theorem prover

49

s.add(sqrt((a.x - b.x)**2 … == 200)s.push()

s.add(a.x == 100, a.y == 100, b.x == 50, b.y == 50)s.check() # => unsat, a.x

s.pop()s.add(a.x == 100, a.y == 100, …)

s.check() # => sat

trans

actio

n

50

© 2008 by “GillyBerlin”, flickr.com/photos/gillyberlin/2531057541 (CC BY 2.0)

● Constraint-based programming with object-oriented Python

● Leverage same advanced solver as Babelsberg/JS● Quality of life improvements for the programmer

51


Recommended