FuncDesignerDoc
From OpenOpt
Creating oovars
oovars are Object-Oriented Variables (initially, when FuncDesigner was a part of OpenOpt module - OpenOpt variables). Their creating is performed via oovar() and oovars(). Let's note - each oovar and oofun has field "name", that is used in error messages and for some parts of FuncDesigner kernel. Names should be unique.
from FuncDesigner import * a = oovar() # create oovar a b = oovar('b') # create oovar b named "b" instead of something like "unnamed_34" a, b, c = oovars('a', 'b', 'c') # create 3 oovars named "a", "b", "c", each one can have different size a, b, c, d = oovars(4) # create 4 oovars, each one can have different size myOOVars = oovars(80) # create Python list of 80 oovars, each one can have different size # Beware! - since myOOVars is Python list, math operations differs from array-like: # 2*myOOVars will be list of length 160, myOOVars + someOtherOOVars will be concatenated list etc # You should perform operations like listOfDoubledValues = [2*myOOVars[i] for i in xrange(len(myOOVars))]
Also you can define general n-dimensional numpy arrays of oovars (requires good knowledge of numpy masked arrays), each one of different size. However, for the sake of calculations speed it is recommended to create lots of oovars with size 1, as well as a small number of oovars with huge sizes and involving lots of slicing/indexation operations (like x[i], x[i:j]).
If you want to raise a error when in a point involved size of oovar differs from the expected one, you could use parameter "size" in oovar constructor, e.g.
a = oovar(size=some_expected_size) # or with user-provided name a = oovar('a', size=4)
Creating oofuns
Functions applied on oovars produce oofuns, eg
from FuncDesigner import * a, b, c, d = oovars(4) f1, f2 = a + 2*b + c, sin(a)+cos(b)+arctan(c) f3 = f1 + 4*f2 + cos(f1*f2) # you can involve in-place operations: f3 += sin(f2)+cos(f3) f3 /= 1024 f3 **= 2
To obtain value of oofun we should define a point that is a Python dictionary with keys oovars and values = their values, e.g.
from numpy import * point1 = {a:1, b:[2,3,4], c:array([10,20,30])} r = f3(point1) print(r) # It should print [ 21.60063885 33.28742147 45.64660797]
Some notes:
- Python lists (like the one above with value [2,3,4]) are simpler to type but are not recommended - one can forget to cast them into arrays, involve [1,2] + [5,10] and obtain [1,2,5,10] instead of [6, 12]. In openopt, scipy, lots of other Python scientific packages lists are cast to numpy arrays, yet one cannot be sure there are no any bugs lurking. So, creating Python lists of oovars (eg a=oovars(N)) is not recommended as well unless you exactly know what are you doing.
- You can use summation of Python lists made of oovars/oofuns/numbers/arrays eg
datalist = [a, b, some_oofun_1, 2.0, N, 4, some_oofun_2] # (*) # or datalist = [a, [1,2,3], some_oofun_3] # (**) c = sum(datalist)
- provided all elements under sum() have same size (1 for (*) and 3 for (**)).
- You can use "for" cycle, eg However, currently using "for elem in an_Iterable: result += func(elem)" works much slower than sum([func(elem) for elem in an_Iterable]). It is intended to be fixed in future FD versions.
M = 15; for i in xrange(M): f3 = 0.1*f3+0.2*f2+1
- We can use "while" cycle as well, provided arguments in "for"/"while" loops are independent on any oovar/oofun.
To name an oofun (for some error messages and/or debug purposes) you can use one of the 2 ways below:
K = 0.5*m*V**2; K.name = 'kinetic energy'; P = g*h*m; P.name = 'potential energy'; TotalEnergy = K + P TotalEnergy = (0.5*m*V**2)('kinetic energy') + (m*g*h)('potential energy')
Each oofun should return a single number or array with ndim <= 1; oovars also should be numbers or arrays with ndim <= 1.
Currently only basic numpy operators and functions have been overloaded, if you have something exotic like gamma-function or arctan2 you should create your own oofun (and provide analytical derivative for that one, if it is possible/has sense), see Creating special oofuns.
Automatic differentiation
We can get oofun derivative in a point via automatic differentiation (not to be confused with Numerical differentiation via finite-differences derivatives approximation and symbolic differentiation provided by Maxima, SymPy etc).
from FuncDesigner import * a, b, c = oovars('a', 'b', 'c') f1, f2 = sin(a) + cos(b) - log2(c) + sqrt(b), sum(c) + c * cosh(b) / arctan(a) + c[0] * c[1] + c[-1] / (a * c.size) f3 = f1*f2 + 2*a + sin(b) * (1+2*c.size + 3*f2.size) f = (2*a*b*c + f1*f2 + f3) point = {a:1, b:2, c:[3, 4, 5]} # however, you'd better use numpy arrays instead of Python lists print(f(point)) print(f.D(point)) print(f.D(point, a)) print(f.D(point, [b])) print(f.D(point, fixedVars = [a, c])) """ Expected output: [ 48.9337138 18.16255336 -11.32129756] {a: array([ 51.75779959, 70.89020412, 91.93551537]), b: array([-38.10565554, -54.41138045, -74.08378522]), c: array([[-29.52297003, 2.03660168, 0.67886723], [ -1.60462289, -31.15709726, -0.42789944], [ -4.82390384, -3.85912307, -30.54104071]])} {a: array([ 51.75779959, 70.89020412, 91.93551537])} {b: array([-38.10565554, -54.41138045, -74.08378522])} {b: array([-38.10565554, -54.41138045, -74.08378522])} """
You may be interested in using parameter "exactShape" (default: False). Set it True to prevent flattering/squeezing operations (DerApproximator has the parameter as well).
(coming) Pay attention: function .D() has optional parameter useSparse, default 'auto'. To allow/deny possibility of involving SciPy sparse matrices in AD engine and result output you can set it to True / False (sometimes automatic selection here is a damned hard task). e.g.
r = f.D(point, fixedVars = [a, c], useSparse=False, exactShape=True)
Also you can involve useSparse parameter for OpenOpt problems coded in FuncDesigner (it also has some autoselect possibilities but they are premature as well).
Operator ifThenElse
You can use operator "ifThenElse" where ordinary Python language operator if-then-else can't deal with condition because it has type oofun (that hasn't been invoked on a point yet and thus preliminary unknown) instead of boolean, e.g.
from FuncDesigner import * a, b = oovars('a', 'b') point = {a:10, b:[2, -5, -4]} f1 = ifThenElse(a+sum(b)<1, 3*a, 15*(a+5/b[0])) f2 = 4*sin(a) + 5 * ifThenElse(a+sum(abs(b))>1, 3, 4*a) f3 = 15 + 25 * ifThenElse(2>1, -100, 3*sum(log2(abs(b)))) f4 = 15 + 25 * ifThenElse(1>2, -100, 3*sum(log2(abs(b)))) for f in [f1, f2, f3, f4]: print(f(point)) print(f.D(point)) #[ 187.5] #{a: array([ 15.]), b: array([-18.75, -0. , -0. ])} #[ 12.82391556] #{a: array([-3.35628612])} #[-2485] #{} #[ 414.14460712] #{b: array([ 54.10106403, -21.64042561, -27.05053202])}
If your condition has type boolean when Python loads it (for example when you have "if myOOFun(startPoint)"), then you can use ordinary python if-elif-...-then-else in spite of type of other data involved.
Passing oovars through ordinary Python functions
oovars can pass through ordinary Python functions
provided FuncDesigner has appropriate overloads for all functions/operators
used inside those ones
from FuncDesigner import * a, b, c = oovars('a', 'b', 'c') def func1(x, y): return x + 4*y func2 = lambda x, y, z: sin(x) + 4*y + 3*cos(z) + func1(x, z) def func3(x, y, z): return x + 2*y + 5*sin(z) + func2(x, z, y) + func1(z, y) print(func3(2, 3, 4)) # Note that func3, func2, func1 could be imported from another file instead of been defined here myFunc = func3(a, b, c) # now myFunc is instance of FuncDesigner class oofun point = {a:2, b:3, c:4} print(myFunc(point)) print('difference: %f'% (func3(2, 3, 4) - myFunc(point))) print('Derivative obtained via Automatic differentiation:') print(myFunc.D(point)) # Expected output: # 48.1553074605 # [ 48.15530746] # difference: 0.000000 # Derivative obtained via Automatic differentiation: # {a: array([ 1.58385316]), b: array([ 9.57663998]), c: array([ 1.7317819])}
Solving systems of linear equations
(requires FD ver >= 0.17, OO ver >= 0.27)
Currently
- it requires OpenOpt installed, maybe in future I'll get rid of the dependence
- available solvers is numpy.linalg.solve (LAPACK routine dgesv, dense systems only) and scipy.sparse.linalg.spsolve (a solver from UMFPACK for sparse systems). Also, you can use your own solver (see example with rendering below).
- Note - time for solving may differ for same SLEs due to different matrices obtained from rendering, because Python 2.6 doesn't has ordered sets (they are present in Python 3.x). Maybe I'll implement fixed rendering in future for 3.x, I don't want to deal with quite difficult walkaround for v. 2.6.
Simple example of solving a SLE by FuncDesigner
from FuncDesigner import * # create some variables a, b, c = oovars('a', 'b', 'c') # or just a, b, c = oovars(3) # Python list of 3 linear equations with 3 variables f = [2*a+3*b-2*c+5, 2*a+13*b+15, a+4*b+2*c-45] # alternatively, you could pass equations (the example below is valid for FD 0.18+): # f = [2*a+3*b-2*c == -5, 2*a+15 == -13*b, a+45==4*b+2*c] # assign SLE linSys = sle(f) r = linSys.solve() A, B, C = r(a, b, c) maxRes = r.ff # max residual # Expected result: # {b: array([-5.]), a: array([ 25.]), c: array([ 20.])}
More advanced SLE example
from FuncDesigner import * n = 100 # create some variables a, b, c = oovar(), oovar(size=n), oovar(size=2*n) # let's construct some linear functions R^i -> R^j # in Python range(m, k) is [m, m+1, ..., m+k-1] f1 = a + sum(b*range(5, n+5)) # R^(n+1) -> R f2 = a + 2*b + c.sum() # R^(2n+1) -> R^n # you could use size of oovars f3 = a + a.size + 2*c.size # R^(2n+1) -> R; a.size and c.size will be resolved into 1 and 2*n f4 = c + range(4, 2*n+4) + f1 + 0.5*f2.sum() + 4*f3 # We can use "for" cycle: for i in xrange(4): f4 = 0.5*f4 + a + f1 + 1 # Also, we could use matrix multiplication, eg f5 = dot(someMatrix, f4): rng = 1.5 + cos(range(2*n)).reshape(-1, 1) # define 2n x 1 vector R = dot(rng, rng.T) # create a matrix of shape 2n x 2n f4 = dot(R, f4) # involve matrix multiplication # Create Python list of linear equations f = [a+f4+5, 2*a+b*range(10, n+10)+15, a+4*b.sum()+2*c.sum()-45] # alternatively, you could pass equations: #f = [a+f4==-5, 2*a+b==-15, a==-4*b.sum()-2*c.sum()+45] linSys = sle(f) r = linSys.solve() # get result A, B, C = r(a,b,c) # A, B, C will be numpy arrays of sizes 1, n, 2*n # if failed to solve - A, B, C will contain numpy.nan(s) print('A=%f B[0]=%f C[15]=%f' % (A, B[0], C[15])) # for n=100 it prints A=-5.000000 B[0]=-0.500000 C[15]=-1883724.947909 # you could get residuals # here it will be Python list of length 3 # that is number of (vectorized) equations # with elements of type numpy.array maxResidual = r.ff residuals = [F(r) for F in f]
Example of solving sparse SLE
(requires SciPy installed)
from FuncDesigner import * from time import time t = time() n = 1000 a, b, c = oovar(), oovar(size=n), oovar(size=2*n) f1 = a + sum(b*range(5, n+5)) f2 = a + 2*b + c.sum() f3 = a + a.size + 2*c.size f4 = c + range(4, 2*n+4) + 4*f3 f = [a+f4+5, 2*a+b*range(10, n+10)+15, a+4*b.sum()+2*c.sum()-45] # alternatively, you could pass equations: #f = [a+f4==-5, 2*a+b==-15, a==-4*b.sum()-2*c.sum()+45] linSys = sle(f) r = linSys.solve() # i.e. using autoselect - solver numpy.linalg.solve for dense (for current numpy 1.4 it's LAPACK dgesv) # and scipy.sparse.linalg.spsolve for sparse SLEs (for current scipy 0.8 it's LAPACK dgessv) A, B, C = r(a,b,c) print('A=%f B[4]=%f B[first]=%f C[last]=%f' % (A, B[4], B[0], C[-1])) maxResidual = r.ff # Note - time may differ due to different matrices obtained from SLE rendering # because Python 2.6 doesn't has ordered sets (they are present in Python 3.x) # maybe I'll implement fixed rendering in future for 3.x, I don't want to deal # with quite difficult walkaround for 2.6 print('time elapsed: %f' % (time()-t))
Rendering FuncDesigner SLE into ordinary SLE Ax=b
from FuncDesigner import * # Create some variables a, b = oovars('a', 'b') c = oovar('c', size=2) # Python list of linear equations f = [2*a + 3*b - 2*(c+[1, 2]) * [3, 4] + 5, # R^nVars - > R^2 2*a+13*b+15, # R^nVars - > R a+4*b+2*c.sum()-45# R^nVars - > R ] # Alternatively, you could pass equations: # f = [2*a + 3*b - 2*(c+[1, 2]) * [3, 4]==-5, # 2*a+15==-13*b, a==-4*b-2*c.sum()+45] # Assign SLE linSys = sle(f) # Rendered matrices for Ax=b are linSys.A (numpy.array of shape 4 x 4) and linSys.b (numpy.array of length 4) A, B = linSys.A, linSys.b # B instead b for to prevent overriding the oovar b defined above # However, for large sparse problems they can have other type than numpy.ndarray, # e.g. scipy.sparse lil_matrix or coo_matrix (provided you have scipy installed); # elseware sometimes they can be mere out of your computer RAM. # You could cast it to ndarray for example via myArr = myMatrix.toarray() """ Now let's solve it, for this example I use numpy.linalg.solve (that is default dense solver for FD SLEs) but you can use any other linear solver e.g. cvxopt, scipy.lapack or scipy.sparse solvers, PyAMG etc. Also you can code it (via scipy.sparse or somehow else) into one of common sparse matrices formats, save it into a file and solve it via C, Fortran etc (BTW f2py or Cython could be useful here) Current drawback: for huge sparse SLEs you should have enough memory to handle dense A before casting to sparse will be performed. Maybe I'll fix it in future, currently for my purposes I have no deal with these situations """ from numpy import linalg xf = linalg.solve(A, B) # Decode solution sol = linSys.decode(xf) print(c(sol)[0]) print(sol) # Expected output: # 10.2 # {b: array([-7.72]), c: array([ 10.2, 6.4]), a: array([ 42.68])}
Creating special oofuns
If you have functions beyond current FuncDesigner overloads capabilities and/or want to provide oofun wrappers for funcs written in C, Fortran, other languages, you can create your own oofun(s).
Automatic differentiation will be available provided you have DerApproximator installed (it will substitute finite-differences approximation for oofuns w/o user-supplied derivatives).
Generally creating special oofuns is performed via
from FuncDesigner import oofun my_oofun = oofun(function, input=input, (optional)d=d, args=args)
where function is Python function (or connected to Python by f2py, Cython, ctypes etc),
input is oovar(s)/oofun(s) (list or single element) - input args for the function,
d is derivative (if known),
args are additional arguments for function (if required).
Example:
from FuncDesigner import * from scipy import fft from numpy import arange a, b, c = oovars('a', 'b', 'c') FFT = oofun(lambda x, y, z: (fft((x+2*y+4*z)*arange(16))).real, input=[a, b, c]) f = a**2+b**2+c**2 + FFT point = {a:1, b:2, c:3} print f(point) print f.D(point) # Another way, via creating oofun constructor: from FuncDesigner import * from scipy import fft from numpy import arange, hstack a, b, c = oovars('a', 'b', 'c') myFFT = lambda INPUT: oofun(lambda *args:fft(hstack(args)).real, input=INPUT) f = a**2+b**2+c**2 + myFFT([a+2*b, a+3*c, 2*b+4*c]) point = {a:1, b:2, c:3} print f(point) print f.D(point)
Translator
the feature is coming
You can use FuncDesigner code made from lots oovars with routines that has single input array x (numpy array of size n) (possibly along with a small fixed number of other input arrays), function fun: R^n -> R^m (possibly along with several other funcs like this), optionally with function(s) Dfun: R^n -> R^(m x n) that provide(s) user-supplied derivatives (we will involve FuncDesigner automatic differentiation instead).
Examples: routines from scipy.optimize, especially gradient-based; scipy odr; scipy.interate odeint; maybe some user-defined routines; etc.
Of course, some routines mentioned above are already connected to FuncDesigner but an awful lot of other is not yet and all those thousands of them (especially user-defined) will never be.
from FuncDesigner import * # Creating translator: # Way 1. You can define translator from a set (list, tuple) of oovars a, b, c = oovar(size=3), oovar(size=10), oovar(size=100) d = oovar() # for unsized oovars translator will consider it has size 1 T = ootranslator([a, b, c, d]) # Way 2. You can define translator from a point (Python dict of oovars and their values in the point) a, b, c, d = oovars(4) # create 3 oovars point = {a:[0, 1, 2], b:[1]*10, c:[0]*100, d:1} # size a is 3, size b is 10, size c is 100, size d is 1 # alternatively: # from numpy import * # point = {a:arange(3), b:ones(10), c:zeros(100), d:array(1)} T = ootranslator(point) # Using translator: # First of all let's concider using the routines point2vector and vector2point x = T.point2vector(point) # x is numpy array of length ooT.n = 114 # we can perform some operations on x, e.g. pass to a routine x += 1 + sin(x) # and decode it backward: newPoint = T.vector2point(x) # Now let's involve automatic differentiation # create a func func1 = (sum(a)+2*sum(b)+sum(sin(c)*cosh(d)))**2 # R^ 114 -> R pointDerivative1 = func1.D(point) # Python dict {a: df/da, b:df/db, c:df/dc, d:df/dd} func1_d = T.pointDerivative2array(pointDerivative1) # numpy array of size 114 func2 = a+2*sum(b)+sum(sin(c)*cosh(d)) # R^ 114 -> R^3, because size(a) is 3 and other summation elements are of size 1 pointDerivative2 = func2.D(point) # Python dict {a: df/da, b:df/db, c:df/dc, d:df/dd} func2_d = T.pointDerivative2array(pointDerivative2) # numpy 2d array of shape (3, 114) # Now you could use translator in a routine that consumes function, it derivative and start point as numpy array # e.g. for scipy optimize fmin_bfgs you could write from scipy.optimize import fmin_bfgs objective = lambda x: func1(T.vector2point(x)) x0 = T.point2vector(point) derivative = lambda x: T.pointDerivative2array(func1.D(T.vector2point(x))).flatten() # without flatten it may be 2d array, e.g. 1 x 114 x_opt = fmin_bfgs(objective, x0, derivative) optPoint = T.vector2point(x_opt) # Python dict {a:a_opt, b:b_opt, c:c_opt, d:d_opt} a_opt, b_opt, c_opt, d_opt = optPoint[a], optPoint[b], optPoint[c], optPoint[d] # numpy arrays of size 3, 10, 100, 1
Involving sparsity in ootranslator is possible but is undocumented yet.
Interpolation oofun made from scipy.interpolate.UnivariateSpline
Syntax for this feature will be slightly changed in next FD release.
This is example of using scipy.interpolate.UnivariateSpline. This function has been wrapped by a routine from FuncDesigner and yielded the oofun FuncDesigner.interpolate.scipy_UnivariateSpline with exactly same args/kwargs. See scipy.interpolate.UnivariateSpline doc for whole list of possible parameters. You can easily get derivatives via automatic differentiation and involve the function into numerical optimization problems.
from FuncDesigner import * a, b, c = oovars('a', 'b', 'c') point1 = {a:1, b: 0, c:[1, 2, 3]} mySpline = interpolate.scipy_UnivariateSpline([1, 2, 3, 4], [1.001, 4, 9, 16.01])
(see scipy UnivariateSpline constructor doc for other available arguments)
f = mySpline(a) print(f(point1)) print(f.D(point1)) f2 = a + sin(b) + c[0] + arctan(1.5*f) F = mySpline(a + f2 + 2*b + (c**2).sum()) print(F(point1)) print(F.D(point1)) """ Expected output: [ 1.001] {a: 2.0015} [ 329.61795391] {a: 108.51234473582626, b: 111.39024933951316, c: array([ 111.39024934, 148.52033245, 222.78049868])} """
Integration oofun made from scipy.integrate.quad
(coming)
Currently scipy.intergate.quad is used Fortan library QUADPACK, (R^n->R^1 only), maybe in future higher order solvers will be connected, e.g. scipy.intergate dblquad, tplquad, quadrature, fixed_quad, trapz etc
Usage:
myOOFun = integrator(integration_oofun, domain)
where domain is tuple (integration_oovar, lower_bound, upper_bound)
Examples:
from FuncDesigner import * a, b, c = oovars('a', 'b', 'c') f1, f2 = sin(a)+cosh(b), 2*b+3*c.sum() f3 = 2*a*b*prod(c) + f1*cos(f2) point1 = {a:1, b:2, c:[3, 4, 5]} point2 = {a: -10.4, b: 2.5, c:[3.2, 4.8]} domain = (b, -1, 1) f4 = integrator(f1+2*f2+3*f3, domain) print(f4(point1), f4(point2)) # Expected output: 147.383792876, 102.143425528 # integral bounds can be oofuns as well: f5 = integrator(f1+2*f2+3*f3, (a, 10.4, f2+5*sin(f1))) print(f5(point1), f5(point2)) # Expected output: 404683.969794 107576.397664 from numpy import inf # you can use infinite bounds as well f6 = integrator(1/(1+a**2+b**2+sum(c**2)), (a, 10+cos(f2+2*f3), inf)) print(f6(point1), f6(point2)) # Expected output: 0.0847234400308 0.0905041349188 f7 = integrator(f1+2*f2+3*sqrt(abs(f3)), (a, cos(f2+2*f3), f2+5*sin(f1)) ) print(f7(point1), f7(point2)) # Expected output: 9336.70442146 5259.53130904
Boolean oofuns
Boolean oofuns are created while using operations '>', '>=', == (equals, ".eq" before FD v 0.18), '<', '<=' on ordinary smooth oofuns.
Let's note: since FuncDesigner is intended primarily for smooth functions, currently operations '>' and '>=' are same, as well as '<' and '<=' - all they yield "true" for equality case.
Resulting oofun has output of type boolean (True/False); for vectorized comparisons it is False if any of vector coordinates equals to numpy.nan ("not a number", e.g. result of extracting logarithm or square root from negative number, result of user-defined function) or the constraint is violated:
from FuncDesigner import * a, b = oovars(2) point = {a:[2,4],b:[5,10]} c = a+2*b; print(c(point)) # prints [12. 24.] d = c > 15; print(d(point)) # prints '''False''' because c has coordinate 12 that is less than 15 z = c > -10; print z(point) # prints '''True''' because all c coordinates are greater than -10 in the point involved z2 = log(b-7); print(z2(point)) # prints '''False''' because log([-2,3]) is [NaN, 1.09861229]
Numerical optimization: using OpenOpt
Currently you can use only the classes mentioned below, in nearest future I intend to provide connections and examples for some other OO classes (mb QP, DFP, NLLSP, MINLP).
Let me remember you - required constraints tolerance can be change during prob assignment, e.g. p = NLP(..., contol = 1e-7). See also the doc entry Personal tolerances for constraints.
Using FuncDesigner you don't have to provide 1st derivatives - they are calculated via Automatic differentiation.
If possible, constraints are rendered into box-bound and linear ones.
(coming)You can involve useSparse={True/False} parameter (it has some autoselect possibilities but they are premature).
Suppose you have optimization variables a, b, c = oovars('a', 'b', 'c'). To extract optimal point coordinates anything below works (since FuncDesigner v. 0.27):
a_opt, b_opt, c_opt = r(a, b, c) a_opt, b_opt, c_opt = r(a), r(b), r(c) r('a'), r('b'), r('c') r('a', 'b', 'c') r[a], r[b], r[c] r['a'], r['b'], r['c'] a(r), b(r), c(r)
For the whole list of problems and solvers available in OpenOpt framework see OpenOpt problems tree:
LP
# Example of creating LP in FuncDesigner # and solving it via OpenOpt from FuncDesigner import * from openopt import LP # Define some oovars x, y, z = oovars(3) # Let's define some linear functions f1 = 4*x+5*y + 3*z + 5 f2 = f1.sum() + 2*x + 4*y + 15 f3 = 5*f1 + 4*f2 + 20 # Define objective; sum(a) and a.sum() are same as well as for numpy arrays obj = x.sum() + y - 50*z + sum(f3) + 2*f2.sum() + 4064.6 # Define some constraints via Python list or tuple or set of any length, probably via while/for cycles constraints = [x+5*y<15, x[0]<4, f1<[25, 35], f1>-100, 2*f1+4*z<[80, 800], 5*f2+4*z<100, -5<x, x<1, -20<y, y<20, -4000<z, z<4] # Start point - currently matters only size of variables, glpk, lpSolve and cvxopt_lp use start val = all-zeros startPoint = {x:[8, 15], y:25, z:80} # however, using numpy.arrays is more recommended than Python lists # Create prob p = LP(obj, startPoint, constraints = constraints) # Solve r = p.solve('glpk') # glpk is name of solver involved, see OOF doc for more arguments # Decode solution print('Solution: x = %s y = %f z = %f' % (str(x(r)), y(r), z(r))) # Solution: x = [-4.25 -4.25] y = -20.000000 z = 4.000000
See also another one FD LP example
NLP
from FuncDesigner import * from openopt import NLP a, b, c = oovars('a', 'b', 'c') f = sum(a*[1, 2])**2+b**2+c**2 startPoint = {a:[100, 12], b:2, c:40} # however, you'd better use numpy arrays instead of Python lists p = NLP(f, startPoint) p.constraints = [(2*c+a-10)**2 < 1.5 + 0.1*b, (a-10)**2<1.5, a[0]>8.9, a+b > [ 7.97999836, 7.8552538 ], \ a < 9, (c-2)**2 < 1, b < -1.02, c > 1.01, (b + c * log10(a).sum() - 1) ** 2==0] r = p.solve('ralg') print(r.xf) a_opt, b_opt, c_opt = a(r), b(r), c(r) # For FuncDesigner v >= 0.17.dev you can use any of the following as well: # a_opt, b_opt, c_opt = r(a, b, c) # a_opt, b_opt, c_opt = r(a), r(b), r(c) # r('a'), r('b'), r('c') (provided you have assigned the names to oovars as above) # r[a], r[b], r[c] # r['a'], r['b'], r['c'] # a(r), b(r), c(r) """ Expected output: ... objFunValue: 717.75631 (feasible, max constraint = 7.44605e-07) {a: array([ 8.99999792, 8.87525277]), b: array([-1.01999971]), c: array([ 1.0613562])} """
NSP
# Example for NSP # |x[0]| + 1.2*|x[1]| + 1.44*|x[2]| + ... + 1.2^74*|x[74]| + |y-15| +|y+15| + y^2 + -> min # coded in FuncDesigner # all 1st derivatives are obtained via Automatic Differentiation, # not to be confused with finite-difference derivatives approximation from FuncDesigner import * from openopt import NSP from numpy import cos, arange x, y = oovars('x', 'y') N = 75 koeffs = arange(1, N+1) ** 1.2 f = (abs(x) * koeffs).sum() + abs(y-15) + abs(y+15) + y**2 startPoint = {x: cos(1+arange(N)), y:80} p = NSP(f, startPoint, maxIter = 1e5) r = p.solve('ralg') x_opt, y_opt = x(r), y(r) print(max(abs(x_opt)), y_opt) # Expected output: # 1129 3.000e+01 # istop: 4 (|| F[k] - F[k-1] || < ftol) # Solver: Time Elapsed = 9.27 CPU Time Elapsed = 8.62 # objFunValue: 30.002138 # (9.6909792750048539e-06, array([ 1.03949811e-05]))
NLSP
# Solving system of equations: # x**3 + y**3 - 9 = 0 # x - 0.5*y = 0 # cos(z) + x - 1.5 = 0 from FuncDesigner import * from openopt import NLSP x, y, z = oovars(3) f = (x**3 + y**3 - 9, x - 0.5*y, cos(z) + x - 1.5) startPoint = {x:8, y:15, z:80} p = NLSP(f, startPoint) # optional: we could set some constraints p.constraints = [z<70, z>50, z + sin(x) < 60] r = p.solve('nssolve') # nssolve is name of solver involved, see OOF doc for more arguments print('Solution: x = %f y = %f z = %f' % (x(r), y(r), z(r))) # Expected: # Solution: x = 1.000001 y = 2.000000 z = 57.595865 """ More complex example """ # Solving non-linear system of size 152 from FuncDesigner import * from openopt import NLSP from numpy import arange, ones N = 150 x = oovar(size=N) y,z = oovars(2) f = (x.sum()**2 + y - 100*N, 1e-3*sum(x**2) - 5*y - 100*N - (z-1)**2, (x-15)*arange(N)/(100*N) - 4*z + 5) startPoint = {x: ones(N), y:15, z:80} p = NLSP(f, startPoint, maxIter=1e7) r = p.solve('scipy_fsolve') # nssolve is name of solver involved, see OOF doc for more arguments print('x[5]:%f y:%f z:%f' % (x(r)[5], y(r), z(r))) #x[5]:15.000000 y:-2107.773183 z:1.250000
MILP
# Example of creating MILP in FuncDesigner # and solving it via OpenOpt from FuncDesigner import * from openopt import MILP # Define some oovars x, y, z = oovars(3) # Let's define some linear functions f1 = 4*x+5*y + 3*z + 5 f2 = f1.sum() + 2*x + 4*y + 15 f3 = 5*f1 + 4*f2 + 20 # Define objective; sum(a) and a.sum() are same as well as for numpy arrays obj = x.sum() + y - 50*z + sum(f3) + 2*f2.sum() + 4064.6 # Start point - currently matters only size of variables startPoint = {x:[8, 15], y:25, z:80} # however, using numpy.arrays is more recommended than Python lists # Create prob p = MILP(obj, startPoint, intVars = [y, z]) # Define some constraints p.constraints = [x+5*y<15, x[0]<-5, f1<[25, 35], f1>-100, 2*f1+4*z<[80, 800], 5*f2+4*z<100, [-5.5, -4.5]<x, x<1, -17<y, y<20, -4000<z, z<4] # Solve r = p.solve('glpk') # glpk is name of the solver involved, see OOF doc for more arguments # Decode solution print('Solution: x = %s y = %f z = %f' % (str(x(r)), y(r), z(r))) # Solution: x = [-5. -4.5] y = -17.000000 z = 0.000000
LLSP
from FuncDesigner import * from openopt import LLSP # create some variables a, b, c = oovars('a', 'b', 'c') # or just a, b, c = oovars(3) # start point is unused by lapack_dgelss and lapack_dgelss # but it is required to set dimensions of variables # also, it is used for some converters e.g. r = p.solve('nlp:ralg') startPoint = {a:0, b:0, c:0} # overdetermined system of 4 linear equations with 3 variables # you can use "for" cycle, operations of sum() eg # startPoint = {a:[0,10], b:0, c:0} # f = [sum(a) + i * b + 4 * sqrt(i) * c - 2 * i**2 for i in xrange(40)] f = [2*a+3*b-4*c+5, 2*a+13*b+15, a+4*b+4*c-25, 20*a+30*b-4*c+50] # assign prob p = LLSP(f, startPoint) # solve r = p.solve('lapack_dgelss') # print result print r.xf # Expected output: # {a: array([-0.3091145]), b: array([-0.86376906]), c: array([ 4.03827441])}
GLP
Currently OpenOpt has no GLP solvers connected that are capable of using derivatives, hence FuncDesigner AD features don't benefit you, but for puny problems, where calculations speed isn't of big importance, you can use FuncDesigner for to make your code more clear and short (without code/decode all variables into/from a single optimization vector), also, handling of fixed/unfixed optimization variables is more convenient.
""" GLP (GLobal Problem from OpenOpt set) example for FuncDesigner model: searching for global minimum of the func (x-1.5)**2 + sin(0.8 * y ** 2 + 15)**4 + cos(0.8 * z ** 2 + 15)**4 + (t-7.5)**4 subjected to some constraints See http://openopt.org/GLP for more info and examples. """ from openopt import GLP from FuncDesigner import * x, y, z, t = oovars(4) # define objective f = (x-1.5)**2 + sin(0.8 * y ** 2 + 15)**4 + cos(0.8 * z ** 2 + 15)**4 + (t-7.5)**4 # define some constraints constraints = [x<1, x>-1, y<1, y>-1, z<1, z>-1, t<1, t>-1, x+2*y>-1.5, sinh(x)+cosh(z)+sinh(t) <2.0] # add some more constraints via Python "for" cycle M = 10 for i in xrange(M): func = i*x+(M-i)*y+sinh(z)+cosh(t) constraints.append(func < i+1) # define start point. You can use variables with length > 1 as well startPoint = {x:0, y:0, z:0, t:0} # assign prob p = GLP(f, startPoint, constraints=constraints, maxIter = 1e3, maxFunEvals = 1e5, maxTime = 5, maxCPUTime = 5) #optional: graphic output #p.plot = 1 or p.solve(..., plot=1) or p = GLP(..., plot=1) # solve r = p.solve('de', plot=1) # try other solvers: galileo, pswarm x_opt, y_opt, z_opt, t_opt = r(x, y, z, t) # or # x_opt, y_opt, z_opt, t_opt = x(optPoint), y(optPoint), z(optPoint), t(optPoint)
Optimization with some fixed variables
It is performed via using arguments fixedVars or optVars:
from FuncDesigner import * from openopt import NLP a, b, c = oovars('a', 'b', 'c') startPoint = {a:[100, 12], b:-0.9, c:40} # however, you'd better use numpy arrays instead of Python lists objective = ((a*[1, 2])**2).sum() + b**2 + c**2 # Optional: set come constraints constraints = [a>[1, 1], # it means a[0]>=1, a[1]>=1 (a.sum()+b)**2<10, (a+b+c)**2 < 15] # it means (a[i]+b+c)**2 < 15 for i in {0,1} fixedVars = b # or fixedVars = [b] / c / [b,c] / [a,c] etc p = NLP(objective, startPoint, fixedVars = fixedVars, constraints = constraints) r = p.solve('ralg') print r.xf # Alternatively, you can set optVars instead of fixedVars: optVars = [a, c] # or optVars = [a] / c / [b,c] / [a,c] etc p = NLP(objective, startPoint, optVars = optVars, constraints = constraints) r = p.solve('ralg') print r.xf """ Expected output: ... objFunValue: 5.809995 (feasible, max constraint = 7.62729e-07) {b: -0.90000000000000002, a: array([ 1.00000032, 0.99999924]), c: array([ 0.00068568])} """
Personal tolerances for constraints
Each boolean oofun created by comparison (less, greater, equal) of smooth oofuns has field tol (tolerance, threshold for triggering False), 0 by default (if the oofun is encountered in constraints passed to openopt, it will use value p.contol for these ones). If you change it to other value, FuncDesigner and OpenOpt will use that one. You can assign it directly (c.tol = myValue), but other ways are more convenient and recommended:
from FuncDesigner import * a = oovar() point = {a: 1.5001} # you can use any of the ways below c = a<1.5; c.tol = 1e-3 # direct way, inconvenient - you have to type 'c' 2 times, and sometimes name is much longer. c = (a<1.5)(tol=0.001) # to change tolerance you can call it with keyword arg '''tol''' # in the __call__ routine you can assign constraint name as well: c = (a<1.5)('asdf', tol=0.001) # (*) c = (a<1.5)('asdf')(tol=0.001) # (**) c = (a<1.5)(name='my constraint', tol=0.001) c = (a<1.5)(tol=0.001, name='asdf') c = (a<1.5)(name='asdf')(tol=1e-3) # Most recommended ways are (*) and (**) d = (a<1.5)(tol=0.000001) print('c: ' + str(c1(point)) + ' d: ' + str(d(point))) # c: True d: False print(a+2==3.50005)(tol=1e-10)(point) # False
Providing adequate constraints tolerances is highly important for most of numerical optimization solvers. Lots of them will have difficulties with constraints of huge typical values (like 103, 104 etc) while required tolerances for them are small (like 10-6, that is default for OpenOpt); or you have some of your constraints to be satisfied very exactly (like 10-20) while others are huge as above. Thus, when you assign your own tolerances, OpenOpt will scale it automatically to the working tolerance p.contol (via multiplying it with a known constant number).
If you have strict constraint, that cannot be violated for any tol (e.g. x>=0 for to keep x0.25 inside definition domain) but you still want to provide a non-default scaling factor for the constraint, you can use negative tol, e.g.
from FuncDesigner import * from openopt import NLP a = oovar() objective = log(a-10000000000) + 100*exp(1/(a-10000000000)) # numbers around 10^10 dollars are involved c = sqrt(a) > 1e5 + 0.1 c2 = (a>10000000000)(tol = -1000) # tolerance is 1000 dollars, strict inequation to keep objective defined startPoint = {a:10000000001} p = NLP(objective, startPoint, constraints = [c, c2], maxIter = 1e4, ftol = 1e-4) r = p.minimize('ipopt') print(r.ff) # My computer with IPOPT 3.8.0 prints 109.90849 for tol = -1000 # that is better than 109.9183 for tol = 1e-6 (default)
In future some FD functions will have automatically accompanied domain constraints.
Known issues: currently assigning non-zero tol forces box-bound constraints (lb <= x <= ub) to be rendered as general linear (Ax<=b); to be fixed in future.
Attached constraints
You can use attach constraints to oovar, oofun or another constraint; then every time this element is involved in numerical optimization by openopt, the constraints will be connected and involved as well. It is automatically done for some FD functions (log, sqrt, pow, arcsin, arccos) (like (x>0)(tol = -1e-7), (x>1e-300)(tol = -1e-7) etc. Negative tolerance means strict requirement, see related doc entry for details
Sometimes attached constraints are very convenient, e.g. when several programmers work on several files, or for deeply nested recursive system like modelling of propeller blade/whole propeller/turbine/whole aircraft. In future ooSystem (that is under development for now) will also have similar feature.
I illustrate using attached constraints by the openopt NLP example below, but it can be used with other classes as well - LP, MILP, NLSP, GLP etc.
from FuncDesigner import * from openopt import NLP a, b, c = oovars('a', 'b', 'c') b.attach(b < -1.01) a.attach(a<9, a[0]>8.9) # having sqrt(c) we don't need to mention c>0 - it will be connected automatically c.attach((c-2)**2 < 1, c*sqrt(c) <2) sumAB=(a+b > [ 7.97999836, 7.8552538 ])('sum_a_b', tol=1.00000e-12) # having log(a) we don't need to mention a>0 - it will be connected automatically (a>10^-300) sumAB.attach(((b + c * log10(a).sum() - 1) ** 2==0)(tol=1e-7)) f = (sum(a*[1, 2])**2+b**2+c**2).attach((2*c+a-10)**2 < 1.5 + 0.1*b) # we can use operation "attach" several times f.attach((a-10)**2<1.5, (a-10.5)**2<2.5) startPoint = {a:[100, 12], b:2, c:-40} # however, you'd better use numpy arrays instead of Python lists p = NLP(f, startPoint, constraints = sumAB) r = p.minimize('ralg') print(r.xf) a_opt, b_opt, c_opt = r(a, b, c) """ Expected output: ... objFunValue: 721.86104 (feasible, max(residuals/requiredTolerances) = 0.257482) {a: array([ 8.98999844, 8.91886164]), b: array([-1.00999974]), c: array([ 1.0555967])} """
Future work on attached constraints to be done:
- possibility to drop attached constraints (e.g. you want to solve linear system on them and only after that check is it feasible wrt the constraints, or you are sure your solution will satisfy the constraints and thus they may be skipped to speed up or simplify solving).
- possibility to change tol / scale factor (currently mostly tol = -1e-13 is used for log/log2/log10/sqrt)
- provide attached constraints for those splines where user want to have extrapolation disabled
- provide special operator for attaching. This is intended for Python 3.x series, where Unicode is available, I don't want any programmers to be misleaded by observing +=, &= etc in FD code related to attached constraints
- currently the element with some attached constraints is evaluating despite of their values; maybe attached constraints should be separated into 2 groups, e.g. "required" and "related". If any "required" constraints in a point isn't satisfied, then for the point we don't calculate the element it is attached to, and return numpy.nan instead.
Solving ODE
- FuncDesigner Automatic differentiation makes the soft very suitable for solving ODE systems
- Another pro is having several variables instead of y and t only, as it is done in most ODE soft tools
- Currently the only one solver available is scipy.integrate.odeint (lsoda from the FORTRAN library odepack), but any other could be connected.
- I haven't decided yet which way to pass non-default arguments to the solver (ml=None, mu=None, rtol=None, atol=None, tcrit=None, h0=0.0, hmax=0.0, hmin=0.0, ixpr=0, mxstep=0, mxhnil=0, mxordn=12, mxords=5, printmessg=0), but it could be easily done.
- If you have parts of code written in C, Fortran or undifferentiable in any other way, you could wrap them into oofun and finite-differences derivatives approximation will be involved, see Creating special oofuns
Basic example
from FuncDesigner import * from numpy import arange # create some variables x, y, z, t = oovars('x', 'y', 'z', 't') # or just x, y, z, t = oovars(4) # Python dict of ODEs equations = { x: 2*x + cos(3*y-2*z) + exp(5-2*t), # dx/dt y: arcsin(t/5) + 2*x + sinh(2**(-4*t)) + (2+t+sin(z))**(1e-1*(t-sin(x)+cos(y))), # dy/dt z: x + 4*y - 45 - sin(100*t) # dz/dt } startPoint = {x: 3, y: 4, z: 5} timeArray = arange(0, 1, 0.01) # 0, 0.01, 0.02, 0.03, ..., 0.99 # assign ODE. 3rd argument (here "t") is time variable that is involved in differentiation. myODE = ode(equations, startPoint, t, timeArray) r = myODE.solve() X, Y, Z = r(x, y, z) print(X[50:55], Y[50:55], Z[50:55]) print(r.msg) # r.extras.infodict contains whole scipy.integrate.odeint infodict """ (array([ 95.32215541, 97.80251715, 100.32319065, 102.88116657, 105.47545552]), array([ 50.26075725, 52.20594513, 54.2008745 , 56.24637088, 58.34441539]), array([ 44.40064889, 46.96317981, 49.62269611, 52.38990533, 55.2741921 ])) Integration successful. """
Advanced example
from FuncDesigner import * from numpy import arange, zeros, ones N = 50 # create some variables x, t = oovars('x', 't') y = oovar('y', size = N) z = oovar('z', size = 2*N) # Python dict of ODEs equations = { x: 2*x + exp(5-2*t), # 1 equation dx/dt y: arcsin(t/5) + z[4] + 2*cos(y) + cos(sum(z)), # N equations dy/dt z: cos(z/10) + y[5] + sin(x) + 4*cos(y.sum()) - 0.001*sinh(2*t) # 2N equations dz/dt } startPoint = {x: 3, y: 4*sin(arange(N)), z: 5*cos(arange(2*N))} timeArray = arange(0, 1, 0.01) # 0, 0.01, 0.02, 0.03, ..., 0.99 # assign ODE. 3rd argument (here "t") is time variable that is involved in differentiation. myODE = ode(equations, startPoint, t, timeArray) r = myODE.solve() X, Y, Z = r(x, y, z) # X.size = 100, Y.shape = (50, 100), Z.shape = (100, 100) print(r.msg) # r.extras.infodict contains whole scipy.integrate.odeint infodict
oosystem
oosystem is a set of some oofuns with set of some constraints; each one can have some attached constraints (some of them as well may have some attached constraints recursively). Maybe in future will be implemented possibility of creating oosystems recursively, merging, uniting etc.
To create an oosystem just pass some oofuns into constructor:
S = oosystem(f, f2, f3) # or any iterable (list, tuple, set) of them: dataToObserve = [f, f2, f3] S = oosystem(dataToObserve)
Later you could attach some more oofuns to the oosystem via
S += elem<br> # or any iterable: S += [elem1,elem2,...]
To attach constraints use &=, e.g.
S &= a<10 S &= (a<15)('constraint A1') cons_B, cons_C = b>1, (c < 39.9999995)('cons_C', tol=1e-4) S &= (cons_B, cons_C)
Let's investigate system state in a point:
ss = S(point)
To see state of the system in a point use print(ss), it would show something like
func3 = nan func2 = 16993.0 func1 = 16980.0
You can extract values of a oofun from the system in a given point via using ss() on the oofun(s):
print(ss(f)) print(ss(f,f2))
To check are all constraints satisfied and no NaN, +/- inf in involved data use the field isFeasible:
print(ss.isFeasible) # True or False
Temporary feature (will be reworked in future to more convenient way): to see names of active constraints use print(ss.activeConstraints)
Numerical optimization of oosystems
- oosystems can automatically determine is your problem LP or NLP; in future some other classes are intended to be involved (MILP, MINLP, QP, QCQP, SDP, maybe more).
Let's consider appropriate examples.
- sometimes fixing some oovars leads a problem from NLP to LP; currently FuncDesigner can't make use of it, but maybe it will be done in future.
Nonlinear example
from FuncDesigner import * a, b, c = oovars('a', 'b', 'c') f = (sum(a*[1, 2])**2+b**2+c**2)('f') another_func = 2*f + 100 + log(c-1.075) # this func has attached constraint c > 1.075 with a negative tol ~ -1e-7 startPoint = {a:[100, 12], b:2, c:40} # however, you'd better use numpy arrays instead of Python lists S = oosystem(f, another_func) # add some constraints S &= [(2*c+a-10)**2 < 1.5 + 0.1*b, (a-10)**2<1.5, c**c<3, a[0]>8.9, (a+b > [ 7.97999836, 7.8552538 ])('sum_a_b', tol=1.00000e-12), a < 9, b < -1.02] r = S.minimize(f, startPoint) # you could use S.maximize as well # default NLP solver is ralg; # to change solver you can use kwarg "solver", e.g. # r = S.minimize(f, startPoint, solver = 'ipopt') # also you can provide any openopt kwargs: # r = S.minimize(f, startPoint, xtol=1e-7, ftol = 1e-7, maxTime = 1e3, ...) print(r.xf) a_opt, b_opt, c_opt = r(a, b, c) # Expected output: # ... # objFunValue: 717.78622 (feasible, max(residuals/requiredTolerances) = 0.786102) # {a: array([ 8.99999758, 8.87525302]), b: array([-1.01999921]), c: array([ 1.07534812])}
Linear example
from FuncDesigner import * a, b, c = oovars('a', 'b', 'c') f = sum(a*[1, 2])+2*b+4*c another_func = 2*f + 100 # doesn't matter in the example startPoint = {a:[100, 12], b:2, c:40} # for to ajust sizes of the variables S = oosystem(f, another_func) # add some constraints S &= [2*c+a-10 < 1500+0.1*b, a-10<150, c<300, a[0]>8.9, c>-100, (a+2*b > [ 7.9, 7.8 ])('sum_a_b', tol=1.00000e-12), a > -10, b > -10, b<10] for i in xrange(1000): S &= b+i*c > 10*i + sum(a) r = S.minimize(f, startPoint, solver='lpSolve') # you could use S.maximize as well # default LP solvers are (sequentially, if installed): glpk, lpSolve, cvxopt_lp, lp:ipopt, lp:algencan, lp:scipy_slsqp, lp:ralg # to change solver you can use kwarg "solver", e.g. # r = S.minimize(f, startPoint, solver = 'cvxopt_lp') # also you can provide any openopt kwargs: # r = S.minimize(f, startPoint, iprint=-1, ...) print(r.xf) a_opt, b_opt, c_opt = r(a, b, c) # or any of the following: # a_opt, b_opt, c_opt = r(a), r(b), r(c) # r('a'), r('b'), r('c') (provided you have assigned the names to oovars as above) # r('a', 'b', 'c') # a(r), b(r), c(r) """ Expected output: ... objFunValue: 45.883673 (feasible, max(residuals/requiredTolerances) = 0) {a: array([ 8.9, -10. ]), b: array([ 8.9]), c: array([ 9.79591837])} """
Some issues to be aware
- You can use f.size for oovars/oofuns but not len(f)
- You can use the following indexation for oofuns/oovars: a[ind], a[ind1:ind2]. ind1 and ind 2 should be integers, positive or negative; you can't use a[ind:] or a[:ind] for now, and currently ind cannot be oovar/oofun, like a.size. Latter is intended to be fixed in future releases.
- some_oovar[some_ind] < some_val is rendered into general linear constraint, not box-bound; the one is intended to be solved in future FuncDesigner/OpenOpt releases, alone with some other features/enhancements to be done.
- FuncDesigner creates lots of auxilary functions and class objects. Since CPython interpreter is slow in comparison with C/C++/Fortran etc, if you have problems with calculations speed you could be interested in Unladen Swallow project, they say it already works with NumPy/SciPy and promise even more speed in release 2009Q3.


