Minimise multiple parameters whilst keeping the same ratios between the parameters - python

I have a particular function which calculates average cost of electricity ($/MWh) over the lifetime of a power plant.
An example function looks like this
def calc(a,b,c):
res = 65*a+74*b+12*c
return res
Where a b and c are cost parameters, such as operating expenditure, construction cost and insurance.
I could vary a b and c in an infinite number of ways, but I would like to keep the ratios the same as an example data point I have, with a lower result for average cost of electricity.
For example
When a=1, b=2 and c=3, res = 249.
However, I would like to find out the optimal values, which keeps the same original ratios, for a b and c when res=600
I have tried to figure out a way to do this using scipy.optimize, but with some difficulty.
I'm not sure how I would program in the ratios for the constraints.
Many thanks.

Let's say you have two sets of values, (a_old, b_old, c_old) and (a_new, b_new, c_new). If you want their respective ratios to be the same (e.g., a_old:c_old is the same as a_new:c_new, and c_old:b_old is the same as c_new:b_new, and so on), then that's the same as saying there exists some constant k such that a_new = k*a_old, b_new = k*b_old, and c_new = k*c_old.
In your example, 65*a_old + 74*b_old + 12*c_old = 249. If you multiply both sides of this equation by k, you get
65(k*a_old) + 74(k*b_old) + 12(k*c_old) = 249*k. This is the same as '65(a_new) + 74(b_new) + 12(c_new) = 249k'.
You want 249*k to be equal to 600. Therefore, k = 600/249 = about 2.4096. You can then use this k value along with a_old, b_old, c_old to find the values of a_new, b_new, c_new. Remember the new values are just k times the old values.
Here's a function that returns the set of scaled parameter values:
def optimize(a,b,c, opt_res):
res = 65 * a + 74 * b + 12 * c
k = opt_res/res
new_vals = [parameter * k for parameter in [a,b,c]]
return new_vals
print(optimize(1,2,3,600.0))
## output: [2.4096385542168677, 4.819277108433735, 7.2289156626506035]
Note I used "600.0", not "600". This forces Python to use floats instead of doing everything with truncated integers.

From this answer, you can specify the constraints like this:
cons = [{'type':'eq', 'fun': con1},
{'type':'eq', 'fun': con2}]
and use the minimize function like this:
scipy.optimize.minimize(func, x0, constraints=cons)

I managed to come to a solution which helped my particular use-case, even though it was pointed out that there was a simpler solution for this particular example.
from scipy.optimize import minimize
import numpy as np
a = 1
b = 2
c = 3
def calc(x):
res = 65*x[0]+74*x[1]+12*x[2]
return res
cons = [{'type': 'eq', 'fun': lambda x: x[0]/x[1]-a/b},
{'type': 'eq', 'fun': lambda x: x[1]/x[2]-b/c},
{'type': 'eq', 'fun': lambda x: calc(x)-600}]
start_pos = np.ones(3)*(1/6.)
print(minimize(calc, x0=start_pos, constraints=cons))
The constraints keep the same ratios, and set the result of calc to equal 600.

Related

Function does not return the correct value unless I include a print statement

I am working through translating some Python code which makes use of a golden search algorithm to find a minimizer of a function. The issue is that the algorithm does not return the correct minimizer unless I uncomment both print functions, print('in gold', obj(c,*args), yc, 'in gold') and print('in gold', obj(d,*args), yd, 'in gold'). This is exclusive to Python as I have translated this exact algorithm to Fortran and it works beautifully.
def optimizer(obj, a, b, args=(), tol=1e-6):
""" golden section search optimizer
Args:
obj (callable): 1d function to optimize over
a (double): minimum of starting bracket
b (double): maximum of starting bracket
args (tuple): additional arguments to the objective function
tol (double,optional): tolerance
Returns:
(float): optimization result
"""
inv_phi = (np.sqrt(5) - 1) / 2 # 1/phi
inv_phi_sq = (3 - np.sqrt(5)) / 2 # 1/phi^2
# a. distance
dist = b - a
if dist <= tol:
return (a+b)/2
# b. number of iterations
n = int(np.ceil(np.log(tol/dist)/np.log(inv_phi)))
# c. potential new mid-points
c = a + inv_phi_sq * dist
d = a + inv_phi * dist
yc = obj(c,*args)
#print('in gold', obj(c,*args), yc, 'in gold')
yd = obj(d,*args)
#print('in gold', obj(d,*args), yd, 'in gold')
# d. loop
for _ in range(n-1):
if yc < yd:
b = d
d = c
yd = yc
dist = inv_phi*dist
c = a + inv_phi_sq * dist
yc = obj(c,*args)
else:
a = c
c = d
yc = yd
dist = inv_phi*dist
d = a + inv_phi * dist
yd = obj(d,*args)
# e. return
if yc < yd:
return (a+d)/2
else:
return (c+b)/2
Normally, I would just keep the print statements in there, but this is called throughout a larger set of code that I am trying to debug, making the output very costly to sort through. I am using VScode if that helps.
The objective function (obj) is obj_last_period, which is defined as:
def obj_last_period(d,x,par):
""" objective function in last period """
# implied consumption (rest)
c = x-d
return -utility.func(c, d, par)
where utility.func(c, d, par) is defined as:
def func(c, d, par):
return func_nopar(c, d, par.d_ubar, par.alpha, par.rho)
where func_nopar is defined as:
def func_nopar(c, d, d_ubar, alpha, rho):
dtot = d + d_ubar
c_total = c**alpha*dtot**(1.0-alpha)
return c_total**(1-rho)/(1-rho)
par.d_ubar, par.alpha, and par.rho are all global parameters, which have values of 0.01, 0.9, and 2, respectively.
As for sample output, for a value of x = 1.1595155892092217, d_low =1e-08, d_high = 1.1595155892092217, the object is called as
d_adj = golden_section_search.optimizer(obj_last_period,d_low,d_high,args=(x,par),tol=par.tol)
and returns a value of d_adj = 0.7166200494040755 (i.e., the minimizer) with no print statements (the associated value of the objective function is 2.1488103454406895 ), which is not the minimum (it actually happens to be the initial value of "c" on line 25 in the def for optimizer, which makes me think the program exits early for some reason?)
When I uncomment the print statements, the program returns d_adj = 0.10695155964918086 (value of objective is 1.1835203405861294), which is the correct minimum. The correct minimum corresponds to the results with my Fortran code so it has to be something specific to Python. I am not an experienced Python user and have really been learning as I go along translating everything into Fortran.
The source code is found at https://github.com/NumEconCopenhagen/ConsumptionSavingNotebooks/tree/master/02.%20DurableConsumptionModel
The specific part of the code that is causing me trouble is in the file "Last_Period.py". I figured sharing the link is likely easier, but if it is easier to just paste all the separate .py files I'm happy to do so!

Finding the minima/maxima of a multi-variable polynomial in python

I have the following polynomial equation that I would like to find the local minima and maxima for.
I defined the function as follows. It uses a flatten function to flatten the nested list, I'll include it for testing purposes (found it here http://rightfootin.blogspot.com/2006/09/more-on-python-flatten.html)
flatten list
from itertools import combinations
import math
def flatten(l, ltypes=(list, tuple)):
ltype = type(l)
l = list(l)
i = 0
while i < len(l):
while isinstance(l[i], ltypes):
if not l[i]:
l.pop(i)
i -= 1
break
else:
l[i:i + 1] = l[i]
i += 1
return ltype(l)
my polynomial
def poly(coefficients, factors):
#quadratic terms
constant = 1
singles = factors
products = [math.prod(c) for c in combinations(factors, 2)]
squares = [f**2 for f in factors]
sequence = flatten([constant, singles, products, squares])
z = sum([math.prod(i) for i in zip(coefficients, sequence)])
return z
The arguments it takes is a list of coefficients, for example:
coefs = [12.19764959, -1.8233151, 2.50952816,-1.56344375, 1.00003828, -1.72128301, -2.54254877, -1.20377309, 5.53510616, 2.94755653, 4.83759279, -0.85507208, -0.48007208, -3.70507208, -0.27007208]
And a list of factor or variable values:
factors = [0.4714, 0.4714, -0.4714, 0.4714]
Plug these in and it calculates the result of the polynomial. The reason I wrote it like this is because the number of variables (factors) changes from fit to fit, so I wanted to keep it flexible. I now want to find the combination of "factors" values within a certain range (let's say between -1 and 1) where the function reaches its maximum and minimum values. If the function was "hard coded" I could use scipy.optimize, but I can't figure out how to make it works as is.
Another option is a brute force grid search (which I use at the moment), but it's very slow as soon as you have more than 2 variables, especially with small step sizes. There may be no true minima/maxima where slope == 0 within the bounds, but as long as I can get the maximum and minimum values that is OK.
Ok, I figured it out. It was two really silly things:
the order of the arguments in the function had to be reversed, so that the first argument (the one I wanted to optimize for) were the "factors" or the X values, followed by the coefficients. That way an array of the same size could be used as the X0 and the coefficients could be used as args.
That wassn't enough, as the function would return an array if an array was the input. I just added a factors = list(factors) to the function itself to put it into the correct shape.
The new function:
def poly(factors, coefficients):
factors = list(factors)
#quadratic terms
constant = 1
singles = factors
products = [math.prod(c) for c in combinations(factors, 2)]
squares = [f**2 for f in factors]
sequence = flatten([constant, singles, products, squares])
z = sum([math.prod(i) for i in zip(coefficients, sequence)])
return z
And the optimization:
coefs = [4.08050532, -0.47042713, -0.08200181, -0.54184481, -0.18515675,
-0.96751856, -1.10814625, -1.7831592, 5.2763512, 2.83505438, 4.7082153,
0.22988773, 1.06488773, -0.70011227, 1.42988773]
x0 = [0.1, 0.1, 0.1, 0.1]
minimize(poly,x0 = x0, args = coefs, bounds = ((-1,1),(-1,1),(-1,1),(-1,1)))
Which returns:
fun: -1.6736636102536673
hess_inv: <4x4 LbfgsInvHessProduct with dtype=float64>
jac: array([-2.10611305e-01, 2.19138777e+00, -8.16990766e+00, -1.11022302e-07])
message: 'CONVERGENCE: NORM_OF_PROJECTED_GRADIENT_<=_PGTOL'
nfev: 85
nit: 12
njev: 17
status: 0
success: True
x: array([1., -1.,1., 0.03327357])

(Python) Markov, Chebyshev, Chernoff upper bound functions

I'm stuck with one task on my learning path.
For the binomial distribution X∼Bp,n with mean μ=np and variance σ**2=np(1−p), we would like to upper bound the probability P(X≥c⋅μ) for c≥1.
Three bounds introduced:
Formulas
The task is to write three functions respectively for each of the inequalities. They must take n , p and c as inputs and return the upper bounds for P(X≥c⋅np) given by the above Markov, Chebyshev, and Chernoff inequalities as outputs.
And there is an example of IO:
Code:
print Markov(100.,0.2,1.5)
print Chebyshev(100.,0.2,1.5)
print Chernoff(100.,0.2,1.5)
Output
0.6666666666666666
0.16
0.1353352832366127
I'm completely stuck. I just can't figure out how to plug in all that math into functions (or how to think algorithmically here). If someone could help me out, that would be of great help!
p.s. and all libs are not allowed by task conditions except math.exp
Ok, let's look at what's given:
Input and derived values:
n = 100
p = 0.2
c = 1.5
m = n*p = 100 * 0.2 = 20
s2 = n*p*(1-p) = 16
s = sqrt(s2) = sqrt(16) = 4
You have multiple inequalities of the form P(X>=a*m) and you need to provide bounds for the term P(X>=c*m), so you need to think how a relates to c in all cases.
Markov inequality: P(X>=a*m) <= 1/a
You're asked to implement Markov(n,p,c) that will return the upper bound for P(X>=c*m). Since from
P(X>=a*m)
= P(X>=c*m)
it's clear that a == c, you get 1/a = 1/c. Well, that's just
def Markov(n, p, c):
return 1.0/c
>>> Markov(100,0.2,1.5)
0.6666666666666666
That was easy, wasn't it?
Chernoff inequality states that P(X>=(1+d)*m) <= exp(-d**2/(2+d)*m)
First, let's verify that if
P(X>=(1+d)*m)
= P(X>=c *m)
then
1+d = c
d = c-1
This gives us everything we need to calculate the uper bound:
def Chernoff(n, p, c):
d = c-1
m = n*p
return math.exp(-d**2/(2+d)*m)
>>> Chernoff(100,0.2,1.5)
0.1353352832366127
Chebyshev inequality bounds P(X>=m+k*s) by 1/k**2
So again, if
P(X>=c*m)
= P(X>=m+k*s)
then
c*m = m+k*s
m*(c-1) = k*s
k = m*(c-1)/s
Then it's straight forward to implement
def Chebyshev(n, p, c):
m = n*p
s = math.sqrt(n*p*(1-p))
k = m*(c-1)/s
return 1/k**2
>>> Chebyshev(100,0.2,1.5)
0.16

Corresponding values of a parameter in Python

I am working in SageMath (Python-based), I am quite new to programming and I have the following question. In my computations I have a quadratic form: x^TAx = b , where the matrix A is defined already as a symmetric matrix, and x is defined as
import itertools
X = itertools.product([0,1], repeat = n)
for x in X:
x = vector(x)
print x
as all combination of [0,1] repeated n times. I got a set of values for b the following way:
import itertools
X = itertools.product([0,1], repeat = n)
results = []
for x in X:
x = vector(x)
x = x.row()
v = x.transpose()
b = x * A * v
results.append(b[0, 0])
And then I defined:
U = set(results)
U1 = sorted(U)
A = []
for i in U1:
U2 = round(i, 2)
A.append(U2)
So I have a sorted set to get a few minimal values of my results. I need to extract minimal values from the set and identify what particular x is corresponding to each value of b. I heard that I can use dictionary method and define preimages in there, but I am really struggling to define my dictionary as {key: value}. Could someone help me please, solve the problem or give me the idea of what direction should I think in? Thank you.

SciPy Minimize with monotonically decreasing Xs constraint

I am looking to do a strenuous optimization in which I use SciPy to optimize discount factors for bond cashflows (application less important, but if interested). So essentially I take multiple known values 'P', where P[i] is a function of C[i] known constant, and array X (X[j]=x(t) where x is a function of time). where the sum-product of C[i] and X = P.
Hope that makes some sense, but essentially in order for a sensible result, I want to put a constraint where X (my array of x values) has the constraint that x[j] < x[j-1], that is, x's are monotonically decreasing.
Here is my code snippet for the optimization function:
In [400]:
import numpy as np
import pandas as pd
import scipy as s
def MyOptimization(X):
P=np.array([99.,100.,105.,110.]) #just example known "P" array, in reality closer to 40 values
c=np.array([1.25,4.,3.1,2.5]) #Cash flows for each P
t=np.array([[1.2,2.,4.,10.0],[0.5,1.],[2.3,5.,10.5],[1.7]]) #time t of each cash flow, multiple per 'P'
#remember P=X(t)*c[i] and x(t) where x[i+1]<x[i]
tlist=[] #t's will be used as index, so pulling individual values
for i in t:
for j in i:
tlist.append(j)
df=pd.DataFrame(data=X,index=tlist).drop_duplicates().sort() #dataframe to hold t (index) and x, x(t), and P(x,c) where c is known
#print df
sse=0
for i in range(0,len(P)):
pxi = np.sum(df.loc[t[i],0].values*c[i])+100*df.loc[t[i][-1],0]
sse=sse+(pxi-P[i])**2 #want to minimize sum squared errors between calculated P(x,c) and known P
return sse
cons=({'type':'ineq','fun': lambda x: x[1] < x[0]}) #trying to define constraint that x is decreasing with t
opti=s.optimize.minimize(MyOptimization,x0=[0.90,0.89,0.88,0.87,0.86,0.85,0.84,0.83,0.82,0.81],bounds=([0,1],)*10,constraints=cons)
In [401]:
opti
Out[401]:
status: 0
success: True
njev: 4
nfev: 69
fun: 5.445290696814009e-15
x: array([ 0.90092322, 0.89092322, 0.88092322, 0.94478062, 0.86301329,
0.92834564, 0.84444848, 0.83444848, 0.96794781, 1.07317073])
message: 'Optimization terminated successfully.'
jac: array([ -7.50609263e-05, -7.50609263e-05, -7.50609263e-05,
-5.92906077e-03, 3.46914830e-04, 9.17475767e-03,
-4.89504256e-04, -4.89504256e-04, -1.61263312e-02,
8.35321580e-03, 0.00000000e+00])
nit: 4
And it is clear to see where in the results the x array is not decreasing. (tried adding (0,1) bounds as well but result failed, so focussing on this for now.
The important line here for the constraint that I'm really not sure about is:
cons=({'type':'ineq','fun': lambda x: x[1] < x[0]})
I tried following the documentation, but clearly it hasn't worked.
Any ideas greatly appreciated.
Let's try
def con(x):
for i in range(len(x)-1):
if x[i] <= x[i+1]:
return -1
return 1
cons=({'type':'ineq','fun': con})
This should reject lists that aren't set up like you want, but I'm not sure is scipy is going to like it.
I can't comment on the post below, but you need to have an i=i in there... tuple([{'type':'ineq', 'fun': lambda x,i=i: x[i] - x[i+1]} for i in range(9)] + [{'type':'eq', 'fun': lambda x,i=i: 0 if x[j] != x[j+1] else 1} for j in range(9)])

Categories