Scipy minimize ignores constraint - python

I have the following code:
def constraint(params):
if abs(params[0] - 15) < 2 and abs(params[1] + 10) < 2:
return -1
else:
return 0
def f(params):
x, z = params
if abs(x - 15) < 2 and abs(z + 10) < 2:
return -9999999
return (x - 15) ** 2 + (z + 10) ** 2 * numpy.sqrt(numpy.abs(numpy.sin(x)))
# Last: 15.00024144, -9.99939634
result = optimize.minimize(f, (-15, -15),
bounds=((-15.01, 15.01,), (-15.01, 15.01,),),
method="SLSQP",
options={'maxiter': 1024 * 1024},
jac=False,
constraints={
'type': 'ineq',
'fun': constraint,
})
print(result)
print(f(result.x))
And it gives the following result:
fun: -9999999.0
jac: array([0., 0.])
message: 'Optimization terminated successfully.'
nfev: 12
nit: 7
njev: 3
status: 0
success: True
x: array([ 15.01 , -11.60831378])
-9999999
The given values [ 15.01, -11.60831378] should be dropped by the constraint (and they were: if I add more verbose logging, I see that constraint function returns -1, but scipy ignores it. Why?
I'm pretty far from data science and maths, so I'm sorry for stupid mistakes if they are there.

To help the algorithm find the right direction, you need to separate your constraints:
def f(params):
print(params)
x, z = params
if abs(x - 15) < 2 and abs(z + 10) < 2:
return -9999999
return (x - 15) ** 2 + (z + 10) ** 2 * numpy.sqrt(numpy.abs(numpy.sin(x)))
# Last: 15.00024144, -9.99939634
result = optimize.minimize(f, (-15, -15),
bounds=((-15.01, 15.01,), (-15.01, 15.01,),),
method="SLSQP",
options={'disp':True, 'maxiter': 1024 * 1024},
jac=False,
constraints=({
'type': 'ineq',
'fun': lambda params : abs(params[0] - 15) -2,
},
{
'type': 'ineq',
'fun': lambda params : abs(params[1] + 10) -2,
},)
)
print(result)
print(f(result.x))
Gives:
Optimization terminated successfully. (Exit mode 0)
Current function value: 6.5928117149596535
Iterations: 6
Function evaluations: 24
Gradient evaluations: 6
fun: 6.5928117149596535
jac: array([-1.2001152, 2.5928117])
message: 'Optimization terminated successfully.'
nfev: 24
nit: 6
njev: 6
status: 0
success: True
x: array([13., -8.])
[13. -8.]
6.5928117149596535
Bingo!

Related

Sympy: possible to make the solver return as soon as a solution is found (before all solutions are found)?

I'm working on a project whereby I am coding a virtual robot arm. Within the script I generate roughly 1350 targets [x, y, z] for the robot's end-effector and then I use the coordinates to calculate 3 angles for the robot's joints. I'm using sympy.solve:
def solve_robot(coordinate_x, coordinate_y):
try:
w, z = sym.symbols('w, z')
# Sympy solver cannot handle trig functions that contain a symbolic and a float.
# Have to round to integer to work around. (Introduces rounding error to calculation).
angle = round(radians(90))
eq1 = sym.Eq(60 * sym.sin(w) + 80 * sym.cos(z - angle), coordinate_x)
eq2 = sym.Eq(37.03 + 60 * sym.cos(w) - 80 * sym.sin(z - angle) - 20, coordinate_y)
result = sym.solve([eq1, eq2], (w, z))
if len(result) > 0: #
omega, beta = result[0] #
omega = round(degrees(omega), 2) # If boom 1 angle smaller than -26 degrees then a collision
if omega < -26.0: # with the robot body will occur. Thus, output invalid result.
result = [] #
return result
except: # (To handle problems) If solver is unable to solve, return empty.
return []
It takes rougly 7 minutes to complete all calculations. I've tried the flags such as manual=True, simplify=False, but it still takes a long time. Almost all my solutions have 2 solutions, is there a way to force sympy to stop after finding one solution? So theoretically the code will be twice as fast?
Edit:
I needed to round the angle otherwise this error occurs:
TypeError: can't convert -oo to int
I'm not sure why you are rounding radians(90). If you want the exact angle 90 degrees in radians then use SymPy's pi/2. Here are your equations:
from sympy import *
x, y, w, z = symbols('x, y, w, z')
angle = pi/2
eq1 = Eq(60 * sin(w) + 80 * cos(z - angle), x)
eq2 = Eq(37.03 + 60 * cos(w) - 80 * sin(z - angle) - 20, y)
eqs = [eq1, eq2]
The pi/2 naturally simplifies so the equations become:
In [17]: eq1
Out[17]: 60⋅sin(w) + 80⋅sin(z) = x
In [18]: eq2
Out[18]: 60⋅cos(w) + 80⋅cos(z) + 17.03 = y
We can solve this kind of system using Groebner bases if we convert to polynomials with the substitutions sw = sin(w), cw = cos(w) etc.:
In [23]: sw, cw, sz, cz = symbols('sw, cw, sz, cz')
In [24]: rep = {sin(w): sw, cos(w): cw, sin(z): sz, cos(z): cz}
In [25]: eqs2 = [eq.subs(rep) for eq in eqs]
In [26]: eqs2
Out[26]: [60⋅sw + 80⋅sz = x, 60⋅cw + 80⋅cz + 17.03 = y]
We now have two equations for four unknowns but we know that we can make new polynomial equations because sin(x)**2 + cos(x)**2 = 1:
In [27]: eqs3 = eqs2 + [Eq(sz**2 + cz**2, 1), Eq(sw**2 + cw**2, 1)]
In [28]: for eq in eqs3: pprint(eq)
60⋅sw + 80⋅sz = x
60⋅cw + 80⋅cz + 17.03 = y
2 2
cz + sz = 1
2 2
cw + sw = 1
We are going to compute a Groebner basis but for that it is much better to have exact rational numbers rather than floats:
In [30]: eqs4 = [nsimplify(eq) for eq in eqs3]
In [31]: eqs4
Out[31]:
⎡ 1703 2 2 2 2 ⎤
⎢60⋅sw + 80⋅sz = x, 60⋅cw + 80⋅cz + ──── = y, cz + sz = 1, cw + sw = 1⎥
⎣ 100 ⎦
Now we can compute a Groebner basis for these polynomials
In [35]: gb = groebner(eqs4, [sw, sz, cw, cz])
In [36]: for eq in gb: pprint(eq)
2 2
⎛1703 4⋅y⎞ x y 1703⋅y 30900209
cz⋅⎜──── - ───⎟ + sw⋅x - ─── + ─── - ────── + ────────
⎝ 75 3 ⎠ 120 120 6000 1200000
2 2
⎛ 1703⎞ x y 1703⋅y 30900209
cz⋅⎜y - ────⎟ + sz⋅x - ─── - ─── + ────── - ────────
⎝ 100 ⎠ 160 160 8000 1600000
4⋅cz y 1703
cw + ──── - ── + ────
3 60 6000
⎛ 2 2 3 2 ⎞
2 ⎛ 2 2 1703⋅y 2900209⎞ ⎜ x ⋅y 1703⋅x y 5109⋅y 36700627⋅y 52623055927⎟
cz ⋅⎜x + y - ────── + ───────⎟ + cz⋅⎜- ──── + ─────── - ── + ─────── - ────────── + ───────────⎟
⎝ 50 10000 ⎠ ⎝ 80 8000 80 8000 800000 80000000 ⎠
4 2 2 2 2 4 3 2
x x ⋅y 1703⋅x ⋅y 97099791⋅x y 1703⋅y 36700627⋅y 52623055927⋅y 9548229
+ ───── + ───── - ───────── - ─────────── + ───── - ─────── + ─────────── - ───────────── + ───────
25600 12800 640000 128000000 25600 640000 128000000 6400000000 256000
16243681
────────
0000000
The final equation is a quadratic in cz. The first 3 are linear in sw, sz and cw (although singular at x=0 which would need to be handled specially). We can therefore compute the solutions like this:
In [40]: gb = groebner(eqs4, [sw, sz, cw, cz])
In [41]: [lsol] = linsolve(gb[:-1], [sw, sz, cw])
In [42]: cz1, cz2 = roots(gb[-1], cz)
In [43]: sol1 = lsol.subs(cz, cz1) + (cz1,)
In [44]: sol2 = lsol.subs(cz, cz2) + (cz2,)
These two expressions sol1 and sol2 are the general form of the solution in terms of the parameters x and y. You can substitute some particular values for those to get numeric answers:
In [49]: sol1.subs({x:100, y:100})
Out[49]:
⎛704201045 8297⋅√4477025624836319 8297⋅√4477025624836319 984201045 5⋅√4477025624836319 11
⎜────────── - ──────────────────────, ────────────────────── + ──────────, ─────────────────── + ──
⎝1013041254 2026082508000 2701443344000 1350721672 1013041254 20
68551214073 1633183214073 5⋅√4477025624836319⎞
───────────, ───────────── - ───────────────────⎟
26082508000 2701443344000 1350721672 ⎠
In [50]: [s.n(3) for s in sol1.subs({x:100, y:100})]
Out[50]: [0.421, 0.934, 0.907, 0.357]
In [51]: [s.n(3) for s in sol2.subs({x:100, y:100})]
Out[51]: [0.969, 0.523, 0.247, 0.852]
Of course these are the answers for sz etc but you want z itself. We can recover z and w using atan2 and then use lambdify for faster evaluation:
In [52]: swsol, szsol, cwsol, czsol = sol1
In [54]: zsol = atan2(szsol, czsol)
In [55]: wsol = atan2(swsol, cwsol)
In [56]: f = lambdify((x, y), (zsol, wsol))
In [57]: f(100, 100)
Out[57]: (1.2058759278150635, 0.4346913079154993)
In [58]: %time f(100, 100)
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 239 µs
Out[58]: (1.2058759278150635, 0.4346913079154993)
In [59]: %time f(102, 95)
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 256 µs
Out[59]: (1.2700124590995348, 0.4406497868037883)
Now you can see that the answer for any given x and y can be computed in less than a millisecond.
In fact you can do all 1350 points in a few milliseconds:
In [60]: x = y = np.linspace(50, 100, 1350)
In [61]: %time f(x, y)
CPU times: user 4 ms, sys: 0 ns, total: 4 ms
Wall time: 3.41 ms
Out[61]:
(array([1.82911048, 1.82883992, 1.82856907, ..., 1.20763722, 1.20675767,
1.20587593]),
array([-0.47322886, -0.47263842, -0.47204816, ..., 0.43240847,
0.43354848, 0.43469131]))

scipy linear programming - formulation of problem

Friends - Can someone help me formulate a LP problem using scipy in python as below, sorry for this naive ask, I am not able to get started at all with this. I could do this in excel, but finding it difficult in python (am new to this library and couldn't solve)
I would be very thankful if someone could help me out please:
This is the data:
This is problem formulated
import pulp as p
import numpy as np
arr = np.array([[0.1167, 2.40, 6.95], [0.1327, 3.44, 15.1], [0.1901, 3.76, 12.7]])
arr = arr.transpose()
# create a problem
Lp_prob = p.LpProblem('Problem', p.LpMinimize)
# create variables
x1 = p.LpVariable("x1", lowBound=0, upBound=np.inf)
x2 = p.LpVariable("x2", lowBound=0, upBound=np.inf)
x3 = p.LpVariable("x3", lowBound=0, upBound=np.inf)
# define problem
Lp_prob += 6.95 * x1 + 15.1 * x2 + 12.7 * x3
# define constraints
Lp_prob += x1 * 0.1167 + x2 * .1327 + x3 * 0.1901 >= 1.95
Lp_prob += x1 * 2.4 + x2 * 3.44 + x3 * 3.76 >= 0
Lp_prob += x1 >= x2
Lp_prob += x1 >= 0
Lp_prob += x2 >= 0
Lp_prob += x3 >= 0
# see the problem created
print(Lp_prob)
status = Lp_prob.solve()
PulpSolverError: Pulp: Error while executing C:\Users\FinanceProfessional\.conda\envs\spyder-env\Lib\site-packages\pulp\apis\..\solverdir\cbc\win\64\cbc.exe
Using scipy
from scipy.optimize import linprog
arr = np.array([[0.1167, 2.40, 6.95], [0.1327, 3.44, 15.1], [0.1901, 3.76, 12.7]])
arr = arr.transpose()
c = arr[-1]
A = [arr[0], arr[1], [1,1,0]]
b = [0.09, 0, 0]
x0_bounds = (0, None)
x1_bounds = (0, None)
x2_bounds = (0, None)
result = linprog(c, A_ub=A, b_ub=b, bounds=[x0_bounds, x1_bounds, x2_bounds], method='revised simplex')
print(result)
con: array([], dtype=float64)
fun: 0.0
message: 'Optimization terminated successfully.'
nit: 0
slack: array([0.09, 0. , 0. ])
status: 0
success: True
x: array([0., 0., 0.])
from scipy.optimize import minimize
a1, a2, a3 = 1167,1327,1907
b1,b2,b3 = 24000, 34400, 36000
c1,c2,c3 = 69500,15100,12700
x = [10000,10000,10000]
res = minimize(
lambda x: c1*x[0]+c2*x[1]+c3*x[2], #what we want to minimize
x,
constraints = (
{'type':'eq','fun': lambda x: x[0]*a1-x[1]*a2}, #1st subject
{'type':'ineq','fun': lambda x: a1*x[0]+a2*x[1]+a3*x[2]-7}, #2st subject
{'type':'ineq','fun': lambda x: b1*x[0]+b2*x[1]+b3*x[2]-0}, #3st subject
{'type':'eq','fun': lambda x: x[0]%5+x[1]%5+x[2]%5-0}, # x1 x2 x3 are multiple of 5
),
bounds = ((0,None),(0,None),(0,None)),
method='SLSQP',options={'disp': True,'maxiter' : 10000})
print(res)
here the output :
> Optimization terminated successfully (Exit mode 0)
> Current function value: 381000000.00006175
> Iterations: 2
> Function evaluations: 9
> Gradient evaluations: 2
> fun: 381000000.00006175
> jac: array([69500., 15100., 12700.]) message: 'Optimization terminated successfully'
> nfev: 9
> nit: 2
> njev: 2 status: 0 success: True
> x: array([ 0., 0., 30000.])
I had to multiplied all value by 10000 to avoid mode 8 as explained here
I hope this is what you needed. However you should try Or-Tools, a CP library powerful and easier than scipy.
edit: answer to comment
here is a link to a google collab as the original poster cannot run this code on his side.

Where am I going wrong in the following LP code?

I am trying to solve an LP problem with two variables with two constraints where one is inequality and the other one is equality constraint in Scipy.
To convert the inequality in the constraint I have added another variable in it called A.
Min(z) = 80x + 60y
Constraints:
0.2x + 0.32y <= 0.25
x + y = 1
x, y <= 0
I have changed the inequality constraints by the following equations by adding an extra variable A
0.2x + 0.32y + A = 0.25
Min(z) = 80x + 60y + 0A
X+ Y + 0A = 1
from scipy.optimize import linprog
import numpy as np
z = np.array([80, 60, 0])
C = np.array([
[0.2, 0.32, 1],
[1, 1, 0]
])
b = np.array([0.25, 1])
x1 = (0, None)
x2 = (0, None)
sol = linprog(-z, A_eq = C, b_eq = b, bounds = (x1, x2), method='simplex')
However, I am getting an error message
Invalid input for linprog with method = 'simplex'. Length of bounds
is inconsistent with the length of c
How can I fix this?
The problem is that you do not provide bounds for A. If you e.g. run
linprog(-z, A_eq = C, b_eq = b, bounds = (x1, x2, (0, None)), method='simplex')
you will obtain:
con: array([0., 0.])
fun: -80.0
message: 'Optimization terminated successfully.'
nit: 3
slack: array([], dtype=float64)
status: 0
success: True
x: array([1. , 0. , 0.05])
As you can see, the constraints are met:
0.2 * 1 + 0.32 * 0.0 + 0.05 = 0.25 # (0.2x + 0.32y + A = 0.25)
and also
1 + 0 + 0 = 1 # (X + Y + 0A = 1)

Maximizing the value of an equation given a constraint in python

There are four variables
(S1, S2, S3, S4)
with the constraint
(S1+S2+S3+S4=100).
There are four given constants (C1, C2, C3, C4). I want to maximize the value of (S1/C1 + S2/C2 + S3/C3 + S4/C4). Here is my code in python:
#!/usr/bin/env python3
import numpy as np
from scipy.optimize import minimize
S0 = [25, 25, 25, 25]
C = [89415,8991,10944,15164]
def objective(S, C):
total = 0
for index in range(4):
total = total + S[index]/C[index]
return -total
def constraint(S):
return (100 - S[0] - S[1] - S[2] - S[3])
b = (0.0, 100.0)
boundaries = (b,b,b,b)
con = ({'type':'eq', 'fun':constraint})
solution = minimize(objective,S0,args=(C),method='SLSQP',bounds=boundaries,constraints=con)
print (solution)
My code is simply returning the initial guess for S as the final result
fun: -0.0069931517268763755
jac: array([-1.11838453e-05, -1.11222384e-04, -9.13742697e-05, -6.59456709e-05])
message: 'Optimization terminated successfully.'
nfev: 6
nit: 1
njev: 1
status: 0
success: True
x: array([25., 25., 25., 25.])
Where am I going wrong?
It looks like the differences in the output values of your functions are within the default tolerance for the optimizer to stop optimizing your function between iterations. Setting your tolerance to a smaller value like 1e-12 helps with this:
solution = minimize(objective,S0,args=(C),method='SLSQP',bounds=boundaries,constraints=con, tol=1e-12)
Result:
fun: -0.01112223334445557
jac: array([ -1.11837871e-05, -1.11222267e-04, -9.13742697e-05,
-6.59456709e-05])
message: 'Optimization terminated successfully.'
nfev: 192
nit: 32
njev: 32
status: 0
success: True
x: array([ 0.00000000e+00, 1.00000000e+02, 3.01980663e-14,
0.00000000e+00])
which is approximately equal to the absolute maximum solution [0,100,0,0].

Update function based on categorical values python

MatchId ExpectedGoals_Team1 ExpectedGoals_Team2 Timestamp Stages Home Away
0 698085 0.8585339288573895 1.4819072820614578 2016-08-13 11:30:00 0 [92, 112] [94]
1 698086 1.097064295289673 1.0923520385902274 2016-09-12 14:00:00 0 [] [164]
2 698087 1.2752442136224664 0.8687263006179976 2016-11-25 14:00:00 1 [90] [147]
3 698088 1.0571269856980154 1.4323522262211752 2016-02-16 14:00:00 2 [10, 66, 101] [50, 118]
4 698089 1.2680212913301165 0.918961072480616 2016-05-10 14:00:00 2 [21] [134, 167]
Here is the function that needs to be updating the outcomes based on the categorized column 'Stages'.
x1 = np.array([1, 0, 0])
x2 = np.array([0, 1, 0])
x3 = np.array([0, 0, 1])
total_timeslot = 196
m=1
def squared_diff(row):
ssd = []
Home = row.Home
Away = row.Away
y = np.array([1 - (row.ExpectedGoals_Team1*m + row.ExpectedGoals_Team2*m), row.ExpectedGoals_Team1*m, row.ExpectedGoals_Team2*m])
for k in range(total_timeslot):
if k in Home:
ssd.append(sum((x2 - y) ** 2))
elif k in Away:
ssd.append(sum((x3 - y) ** 2))
else:
ssd.append(sum((x1 - y) ** 2))
return sum(ssd)
sum(df.apply(squared_diff, axis=1))
For m=1, Out[400]: 7636.305551658377
By assigning an arbitrary value of m for each category in Stages I want to test a cost function. Let m1 = 2, m2 = 3.
Here is how I attempted.
def stages(row):
Stages = row.Stages
if Stages == 0:
return np.array([1 - (row.ExpectedGoals_Team1*m + row.ExpectedGoals_Team2*m), row.ExpectedGoals_Team1*m, row.ExpectedGoals_Team2*m])
elif Stages == 1:
return np.array([1 - (row.ExpectedGoals_Team1*m1 + row.ExpectedGoals_Team2*m1), row.ExpectedGoals_Team1*m1, row.ExpectedGoals_Team2*m1])
else:
return np.array([1 - (row.ExpectedGoals_Team1*m2 + row.ExpectedGoals_Team2*m2), row.ExpectedGoals_Team1*m2, row.ExpectedGoals_Team2*m2])
df.apply(squared_diff, Stages, axis=1)
TypeError: apply() got multiple values for argument 'axis'
df.apply(squared_diff, Stages, axis=1) got error because the second parameter is for axis so it thought axis=Stages, but then the third parameter is again axis=1.
To address the problem, you can first store desired m into a separate column
df['m'] = df.Stages.apply(lambda x: 1 if x == 0 else 2 if x == 1 else 3)
Then replace this line in your squared_diff function
y = np.array([1 - (row.ExpectedGoals_Team1*m + row.ExpectedGoals_Team2*m), row.ExpectedGoals_Team1*m, row.ExpectedGoals_Team2*m])
with
y = np.array([1 - (row.ExpectedGoals_Team1*row.m + row.ExpectedGoals_Team2*row.m), row.ExpectedGoals_Team1*row.m, row.ExpectedGoals_Team2*row.m])

Categories

Resources