0

Nonlinear Programming with Inequality Constraints

==============
Imagine that you work out in a factory that produces some kind of composite products.
Each product has its own composition of parts. Some parts are bought, and some are made at your factory. Those products that are made in your factory have their own production route.
You know the number of your machines in production and the fund of their working hours.
You have a sales plan for the year It contains an order for any product that you produce, terms, quantity of required products and other information.
Having previously received the composition of each required product needed for sale, and having processed the information from the sales plan, you get two tables.
Data on how much time and in what batches each product is processed and on which machine is in the df_t_o_p table.
This data is arranged in an mxn matrix(variable name in code M_cons), where m = 87 are rows (machines) and n = 864 are columns (products). The data table stores has the following data:
-Definition - the name of the children;
-Quantity - the required quantity of the part;
-Dim - units of measure for the part (it doesn't matter at the moment);
-p_i, a_i, b_i - coefficients calculated during data preprocessing;
-Cost - the cost of each part associated with its dimension (That is, there is a kind of automatic normalization here, but we don’t think about it yet.)

Model


Problem statement: It is necessary to find the optimal number of parts that we will produce and store in advance in order to ensure the possibility of fulfilling the entire sales plan. The goal function is actually the minimization of the storage cost function.
The goal function and constraints are described in the code.

from functools import reduce

import cvxpy
from scipy.optimize import Bounds, minimize
from cvxpy import multiply, minimum, maximum, power
import cvxpy as cp
import numpy as np
import pandas as pd

Path_data = 'C:\\your_path\\data.xlsx'
Path_df_t_o_p = 'C:\\your_path\\df_t_o_p.xlsx'

data_xlsx = pd.ExcelFile(Path_data)
df_t_o_p_xlsx = pd.ExcelFile(Path_df_t_o_p)
_data = pd.DataFrame(data_xlsx.parse(data_xlsx.sheet_names[0]))
_df_t_o_p = pd.DataFrame(df_t_o_p_xlsx.parse(df_t_o_p_xlsx.sheet_names[0])).set_index('Index')

data = {
    'Definition': np.array(_data['Definition']),
    'q_i'       : np.array(_data['Quantity']),
    'Dim'       : np.array(_data['Dim']),
    'p_i'       : np.array(_data['p_i']),
    'a_i'       : np.array(_data['a_i']),
    'b_i'       : np.array(_data['b_i']),
    'c_i'       : np.array(_data['Cost'])
}

# M_cons * X <= T_l
# Matrix of ineq_constraints
M_cons = np.array(_df_t_o_p)
# Time fund for each machines mxn = 87x864
# m = 87 - machines
T_l = np.ones(len(_df_t_o_p.index)) * 60 * 360
# n = 864 - details
n = len(data['Definition'])
# 0 <= x_i <= q_i
l_bound = np.zeros(n)
r_bound = data['q_i'] + 0.00000001
bounds = Bounds(l_bound, r_bound)

ineq_constraints = {
    'type': 'ineq',
    'fun' : lambda x: T_l - M_cons.dot(x),
    'jac' : lambda x: -1 * M_cons
}

x0 = data['q_i'] / 2


def f_scipy(x):
    '''
    My model is:
    OBJECTIVE FUNCTION:
        f(x) = \sum_i term1 + term 2 + term3: f(x) ---> min
        where for all i :
            term1 = c_i * a_i * x_i
            term2 = c_i * b_i * (x_i - q_i) * (x_i - q_i > 0) = c_i * b_i * max{x_i - q_i, 0}
            term3 = c_i^k * (q_i - x_i)^2 * (q_i - x_i > 0) = c_i^k * (q_i - x_i) * max{q_i - x_i, 0}
            , k - must be chosen such that if b_i = 0, then x_i Should be equal to q_i in the model solution.
    ---------------------------------------------------------------------------------------------------------
    CONSTRAINTS:
        X = (x_0, x_1, .... , x_n)
        for all i  0 <= x_i <= q_i
        actually the upper bound is Infinity.
        According to the logic of our objective function,
        the solution should not give x_i > q_i.
        Explanations a little later.
        M_cons * X <= T_l
    :param x: np.array   len(x) = n
    :return: float
    '''
    term1 = data['c_i'] * data['a_i'] * x
    term2 = data['c_i'] * data['b_i'] * (x - data['q_i']) * (x - data['q_i'] > 0)
    term3 = data['c_i']**10 * (data['q_i'] - x)**2 * (data['q_i'] - x > 0)
    return np.sum(term1 + term2 + term3)


def model_scipy():
    res = minimize(f_scipy, x0, bounds=bounds)
    tmp = {'Definition': [], 'x_i': []}
    for name, x in zip(data['Definition'], res.x):
        tmp['Definition'].append(name)
        tmp['x_i'].append(x)
    tmp.update({'Quantity': data['q_i'],
                'Dim': data['Dim'],
                'b_i': data['b_i'],
                'c_i': data['c_i']})
    df = pd.DataFrame.from_dict(tmp)
    if reduce(lambda x, y: x*y, res.x == x0):
        print('res.x = x0')
    else:
        print(f"ineq in x0: \n{T_l - M_cons.dot(x0)}\n\n"
              f"ineq in res.x: \n{T_l - M_cons.dot(res.x)}\n\n"
              f"f(x0) = {f_scipy(x0)}\t"
              f"f(res.x) = {f_scipy(res.x)}")
    return df


def f_cvxpy(x):
    '''
    My model is:
    OBJECTIVE FUNCTION:
        f(x) = \sum_i term1 + term 2 + term3: f(x) ---> min
        where for all i :
            term1 = c_i * a_i * x_i
            term2 = c_i * b_i * (x_i - q_i) * (x_i - q_i > 0) = c_i * b_i * max{x_i - q_i, 0}
            term3 = c_i^k * (q_i - x_i)^2 * (q_i - x_i > 0) = c_i^k * (q_i - x_i) * max{q_i - x_i, 0}
            , k - must be chosen such that if b_i = 0, then x_i Should be equal to q_i in the model solution.
    ---------------------------------------------------------------------------------------------------------
    CONSTRAINTS:
        X = (x_0, x_1, .... , x_n)
        for all i  0 <= x_i <= q_i
        actually the upper bound is Infinity.
        According to the logic of our objective function,
        the solution should not give x_i > q_i.
        Explanations a little later.
        M_cons * X <= T_l
    :param x: np.array   len(x) = n
    :return: float
    '''
    result = 0
    for i in range(len(data['Definition'])):
        a_i = data['a_i'][i]
        b_i = data['b_i'][i]
        q_i = data['q_i'][i]
        c_i = data['c_i'][i]
        x_i = x[i]
        term1 = multiply(c_i, multiply(a_i, x_i))
        term2 = multiply(c_i, multiply(b_i, maximum(x_i - q_i, 0)))
        term3 = multiply(power(c_i, 10), power(maximum(q_i - x_i, 0), 2))
        result += term1 + term2 + term3
    return result


def model_cvxpy():
    x = cp.Variable(n)
    print(cp.installed_solvers())
    object = cp.Minimize(f_cvxpy(x))
    prob = cp.Problem(object, [l_bound <= x, x <= r_bound, M_cons @ x <= T_l])
    prob.solve(verbose=True, solver=cp.CVXOPT)
    print('\nThe optimal values is', prob.value)
    print('A solution x is')
    print(x.value)
    print('A dual solution corresponding to the inequality constraints is')
    print(prob.constants[0].dual_value)

I try to use two libraries to solve the optimization problem - scipy and cvxpy. The point is that my objective function is expected to be convex. Therefore, in the Scipy library, you can use the SLSQP method without fear that we will fall into a local extremum. For this reason, I tried the second library, since the problem is convex.
My result after calling this code

import pandas as pd
from Model import model_scipy, model_cvxpy


def print_pd_df(df):
    with pd.option_context('display.max_rows', None, 'display.max_columns', None, 'display.width', None):
        print(df)


if __name__ == '__main__':
    print_pd_df(model_scipy())
    model_cvxpy()

It turns out like this:

f(x0) = 8.050283944386748e+49   f(res.x) = 1.0724365904778775e+41
                 Definition           x_i      Quantity     Dim           b_i            c_i
0                    100740  1.476151e-02  2.952192e-02      М2  0.000000e+00    6250.000600
1                    101096  2.412800e-01  4.825600e-01       М  0.000000e+00    1426.512333
2                    101830  8.800000e-02  1.760000e-01       М  0.000000e+00     578.696400
3                    101831  1.240000e-01  2.480000e-01       М  0.000000e+00    1031.928820
4                    102336  1.160004e-01  2.320000e-01       М  0.000000e+00    4954.878020
5                    102488  3.910975e+04  7.821950e+04      ШТ  2.190669e+03       8.000000
6                    102505  3.910975e+04  7.821950e+04      М2  2.190669e+03      34.725000
7                    102583  1.955487e+03  3.910975e+03      КГ  2.190669e+03     130.000000
8                    102649  9.827521e-02  9.827520e-02      М2  0.000000e+00   56493.969550
9                    104034  7.500000e-02  1.500000e-01       М  0.000000e+00      74.950460
10                   104518  5.000000e-01  1.000000e+00      ШТ  0.000000e+00      18.091700
11                   104595  1.665000e+00  3.330000e+00       М  0.000000e+00      47.517200
12                   104596  1.005000e+00  2.010000e+00       М  0.000000e+00      76.753333
13                   104597  4.350000e-01  8.700000e-01       М  0.000000e+00      75.168600
14                   104598  1.530000e+00  3.060000e+00       М  0.000000e+00      62.760240
15                   104650  3.635030e-02  7.270000e-02       М  0.000000e+00    5378.125000
16                   104651  1.083059e-02  2.160000e-02       М  0.000000e+00    9637.638850
17                   110033  6.000000e-02  1.200000e-01       М  0.000000e+00      12.720611
18                   110034  6.000000e-02  1.200000e-01       М  0.000000e+00      11.985950
19                   110037  6.000000e-02  1.200000e-01       М  0.000000e+00      11.091833
20                   110038  6.000000e-02  1.200000e-01       М  0.000000e+00      10.782600
21                   110039  6.000000e-02  1.200000e-01       М  0.000000e+00       8.230700
22                   111622  9.500000e-02  1.900000e-01       М  0.000000e+00      34.055200
23                   111625  3.150000e-01  6.300000e-01       М  0.000000e+00      80.854000
24                   111626  1.047500e+00  2.095000e+00       М  0.000000e+00      70.287500
25                   111627  1.000000e+00  2.000000e+00       М  0.000000e+00      43.604667
26                   111628  5.875517e+03  1.175103e+04       М  2.190669e+03      35.818400
27                   111630  5.000000e-02  1.000000e-01       М  0.000000e+00     303.040000
28                   112026  2.542671e+05  5.085342e+05       М  2.190669e+03       0.000000
29                   112276  2.738235e+03  5.476470e+03       М  2.190669e+03     569.310000
30                   113066  1.700000e+00  3.400000e+00       М  0.000000e+00     227.298333
31                   114201  1.467500e+00  2.935000e+00       М  0.000000e+00      59.539600
32                   114254  1.799814e+04  3.599627e+04       М  2.190669e+03      39.824875
33                   114400  1.661000e-01  3.322000e-01       М  0.000000e+00    1025.462750
34                   117365  2.750000e-02  5.500000e-02       М  0.000000e+00      53.830000
35                   118160  2.000000e+00  4.000000e+00      ШТ  0.000000e+00       2.364400
36                   118169  2.000000e+00  4.000000e+00      ШТ  0.000000e+00       1.933300
37                   118266  3.500000e-01  7.000000e-01       М  0.000000e+00      64.716700
38                   118268  2.000000e-01  4.000000e-01       М  0.000000e+00      63.644083
39                   118613  1.000000e-01  2.000000e-01       М  0.000000e+00      38.017380
40                   118743  1.000000e-01  2.000000e-01       М  0.000000e+00      39.895000
41                   120619  6.000000e+00  1.200000e+01       М  0.000000e+00       1.500000
42                   121514  2.000000e+00  4.000000e+00      ШТ  0.000000e+00       4.150000
43                   122299  5.000000e-01  1.000000e+00      ШТ  0.000000e+00       3.872900
44                   122710  5.000000e-02  1.000000e-01       М  0.000000e+00      27.270000
45                   124114  5.000000e-01  1.000000e+00      ШТ  0.000000e+00     329.237300
46                   124200  2.000000e+00  4.000000e+00      ШТ  0.000000e+00       2.975000
47                   124201  8.000000e+00  1.600000e+01      ШТ  0.000000e+00       3.518233
48                   124361  1.955890e+04  3.911780e+04       М  2.190669e+03       1.075000
49                   125242  1.243001e-02  1.243000e-02      М2  0.000000e+00   45359.576350
50                   125243  3.383521e-02  3.383520e-02      М2  0.000000e+00   63989.285700
51                   125244  3.294901e-02  3.294900e-02      М2  0.000000e+00   39410.770200
52                   125852  3.129560e+05  6.259120e+05      ШТ  2.190669e+03       1.380025
53                   125853  8.000000e+00  1.600000e+01      ШТ  0.000000e+00       1.339000
54                   125856  1.955942e+05  3.911885e+05      ШТ  2.190669e+03       1.347200
55                   125857  7.350000e+01  1.470000e+02      ШТ  0.000000e+00       1.081967
56                   125858  1.000000e+00  2.000000e+00      ШТ  0.000000e+00       1.341200
57                   125859  1.500000e+00  3.000000e+00      ШТ  0.000000e+00       1.367400
58                   125860  1.000000e+01  2.000000e+01      ШТ  0.000000e+00       1.591250
59                   125863  3.000000e+00  6.000000e+00      ШТ  0.000000e+00       2.283300
60                   125864  4.700000e+01  9.400000e+01      ШТ  0.000000e+00       1.948950
61                   125867  2.000000e+00  4.000000e+00      ШТ  0.000000e+00       4.916700
62                   125870  1.400000e+01  2.800000e+01      ШТ  0.000000e+00      11.322233
63                   125903  5.000000e-01  1.000000e+00      ШТ  0.000000e+00      16.070800
64                   126780  1.000000e+01  2.000000e+01      ШТ  0.000000e+00       0.250000
65                   127259  1.000000e+00  2.000000e+00      ШТ  0.000000e+00       0.741700
66                   127601  1.000000e+00  2.000000e+00      ШТ  0.000000e+00       1.754167
67                   129605  1.850691e-02  3.700000e-02       М  0.000000e+00    7870.538000
68                   130383  8.000000e+00  1.600000e+01      ШТ  0.000000e+00       3.619775
69                   130560  1.010400e-01  2.020800e-01       М  0.000000e+00    1126.219600
70                   130978  1.026615e+00  2.000000e+00      ШТ  0.000000e+00   12058.999400
71                   130998  3.911725e+04  7.823450e+04      ШТ  2.190669e+03      19.000000
72                   131072  1.550000e+01  3.100000e+01      ШТ  0.000000e+00       0.193340
73                   131073  1.173352e+05  2.346705e+05      ШТ  2.190669e+03       0.391700
74                   131075  4.800000e+01  9.600000e+01      ШТ  0.000000e+00       1.241633
75                   131108  5.000000e-01  1.000000e+00      ШТ  0.000000e+00       2.950000
76                   131194  1.260000e-02  2.520000e-02       М  0.000000e+00     450.270200
   0.000000
393                  159925  5.000000e-01  1.000000e+00      ШТ  0.000000e+00     380.000000
394                  159991  2.000000e+00  4.000000e+00      ШТ  0.000000e+00       0.000000
395                  160145  5.000000e+00  1.000000e+01      ШТ  0.000000e+00       0.0
-------------------------------------------------------------------------------
                                Numerical solver                               
-------------------------------------------------------------------------------
(CVXPY) Apr 05 01:01:27 PM: Invoking solver CVXOPT  to obtain a solution.
     pcost       dcost       gap    pres   dres   k/t
 0:  3.1191e+11 -1.7680e+06  7e+57  1e+01  1e-16  1e+00
Traceback (most recent call last):
  File "C:\Users\chamo\PycharmProjects\OptomizeModel\main.py", line 12, in <module>
    model_cvxpy()
  File "C:\Users\chamo\PycharmProjects\OptomizeModel\Model.py", line 140, in model_cvxpy
    prob.solve(verbose=True, solver=cp.CVXOPT)
  File "C:\Users\chamo\PycharmProjects\OptomizeModel\venv\lib\site-packages\cvxpy\problems\problem.py", line 481, in solve
    return solve_func(self, *args, **kwargs)
  File "C:\Users\chamo\PycharmProjects\OptomizeModel\venv\lib\site-packages\cvxpy\problems\problem.py", line 1012, in _solve
    solution = solving_chain.solve_via_data(
  File "C:\Users\chamo\PycharmProjects\OptomizeModel\venv\lib\site-packages\cvxpy\reductions\solvers\solving_chain.py", line 361, in solve_via_data
    return self.solver.solve_via_data(data, warm_start, verbose,
  File "C:\Users\chamo\PycharmProjects\OptomizeModel\venv\lib\site-packages\cvxpy\reductions\solvers\conic_solvers\cvxopt_conif.py", line 202, in solve_via_data
    results_dict = cvxopt.solvers.conelp(c, G, h, dims, A, b,
  File "C:\Users\chamo\PycharmProjects\OptomizeModel\venv\lib\site-packages\cvxopt\coneprog.py", line 1033, in conelp
    W = misc.compute_scaling(s, z, lmbda, dims, mnl = 0)
  File "C:\Users\chamo\PycharmProjects\OptomizeModel\venv\lib\site-packages\cvxopt\misc.py", line 322, in compute_scaling
    W['beta'][k] = math.sqrt( aa / bb )
ZeroDivisionError: float division by zero

Process finished with exit code 1

The fact is that if I put a parameter in the function, I will call the minimize method with parameters

res = minimize(f_scipy, x0, method='SLSQP', constraints=[ineq_constraints],  bounds=bounds)

That as a result for some reason res.x = x0, I don't understand why. And he's ignoring my conditions for the time fund. Here I need help. Plus, my constraint conditions will then also include indicator functions (there will be added time for equipment changeover if we are going to produce a specific part on the machine, now I just want to run the model so that it gives the expected result). Among other things, if I run my function as at the very beginning, just by conditions on x_i, then my answer seems to coincide with the original one, or rather, it really matches, except for those places where the penalty (the 3rd term from the target is called that) overlaps the first two terms and everything must be done there.

Moreover, for parts with b_i = 0, x_i should result in q_i after optimization. This is achieved by the desired degree of c_i in the 3rd term in the target for the desired part. But I don't know how to select this degree programmatically.

The solution by the cvxpy library breaks somewhere inside, there is a division by zero, and other methods from this library either do not work, or give infinity in the answer. I need help, maybe I'm setting the model incorrectly, or is it easier to write the algorithm myself, rather than using ready-made libraries?
How to make scipy.optimize respect inequality constraints?
Please help.
P.S. I am attaching the files data.xlsx
df_t_o_p.xlsx.

  • Guys, I would appreciate any help. I am new to using scipy. The output has been shortened a bit. Sorry for my English:)) – Dmitrij Apr 05 '22 at 10:49
  • my Inequality constraints incompatible . But i don't understand why – Dmitrij Apr 05 '22 at 12:04
  • I found this [link] (https://stackoverflow.com/questions/33511284/scipy-optimize-minimize-method-slsqp-ignores-constraint). But my target function can't do without an indicator, which makes it less smooth. I don't know how to get around this. – Dmitrij Apr 05 '22 at 12:52

1 Answers1

0

This isn't intended to be a complete answer, but it should help you to get started. That being said, I'll only address the scipy part of your question and ignore all the cvxpy stuff.

  • First of all, your initial point x0 is not feasible. This can easily be observed by evaluating your constraints at x0, i.e. np.all(ineq_constraints["fun"](x0) >= 0) yields False. Consequently, SLSQP can't find a (feasible) descent direction in the first iteration and returns your initial point.

  • Next, the max function and your expression (x - data["q_i"]) * (x - data["q_i"] > 0) are not differentiable. All algorithms under the hood of scipy.optimize.minimize assume that the objective and the constraints are continuously differentiable. Either use a smooth approximation of the max function or model the maximum by additional constraints.

  • Last but not least, it's highly recommended to provide exact derivatives. Since you provided the constraint Jacobian, you should also provide the objective gradient in order to obtain acceptable performance.

joni
  • 6,840
  • 2
  • 13
  • 20
  • Thank you very much for your answer. I am racking my brains about the smoothness of the indicator function, but I do not rule out its bypass. The gradient itself, yes, I also know how to count it, as a function given by branches at different intervals. But this indicator also appears there. And about a point, thanks, something I did not think of it. – Dmitrij Apr 06 '22 at 06:50
  • I took advantage of the smooth max-function [LogSumMax](https://en.wikipedia.org/wiki/LogSumExp). – Dmitrij Apr 06 '22 at 10:31