Search code examples
optimizationoctavelinear-programming

Using GNU Octave GLPK to solve an Optimization Problem that has an Absolute Value in the Objective Function


I want to minimize:

|0.2126 * R1 + 0.7152 * G1 + 0.0722 * B1 - 7(0.2126 * R2 + 0.7152 * G2 + 0.0722 * B2) - (7-1) * 0.05|

subject to the constraint:

0.2126 * R1 + 0.7152 * G1 + 0.0722 * B1 - (0.2126 * R2 + 0.7152 * G2 + 0.0722 * B2) >= 0

This is my code so far

ratio = 7;

objective_coefficients  = [ 0.2126,  0.7152,  0.0722, ratio * -0.2126, ratio * -0.7152, ratio * -0.0722, (1 - ratio) * 0.05]';
constraint_coefficients = [ 0.2126,  0.7152,  0.0722,         -0.2126,         -0.7152,         -0.0722];
constraint_right_hand_side_values = [0]';
variable_lower_bounds = [0, 0, 0, 0, 0, 0, 1]';
variable_upper_bounds = [1, 1, 1, 1, 1, 1, 1];
constriant_types = "L";
variable_types = "CCCCCCC";
sense = 1;

parameters.messsage_level = 1;
parameters.iterations_limit = 100;

[xmin, fmin, status, extra] = glpk(objective_coefficients,
                                   constraint_coefficients,
                                   constraint_right_hand_side_values,
                                   variable_lower_bounds,
                                   variable_upper_bounds,
                                   constriant_types,
                                   variable_types,
                                   sense,
                                   parameters);

The result xmin are all 1s.


Solution

  • In practical terms, demonstrating with a similar-smelling scipy.optimize.linprog, you need an objective auxiliary variable that's minimized with coefficient 1, having 0 for all other objective coefficients. Add constraint rows so that the objective auxiliary is greater than both the absolute argument and the absolute negative argument; this leaves you with

    import numpy as np
    from scipy.optimize import linprog
    
    ratio = 7
    
    objective_coefficients = np.array([0, 0, 0, 0, 0, 0, 1])
    constraint_coefficients = np.array([
        [-0.2126, -0.7152, -0.0722,        0.2126,         0.7152,        0.0722,  0],  # -ax <= 0
        [ 0.2126,  0.7152,  0.0722, -ratio*0.2126, -ratio *0.7152, -ratio*0.0722, -1],  #  bx + c <= obj :  bx - obj <= -c
        [-0.2126, -0.7152, -0.0722,  ratio*0.2126,  ratio *0.7152,  ratio*0.0722, -1],  # -bx - c <= obj : -bx - obj <=  c
    ])
    constraint_right_hand_side_values = np.array([
        0,
         ratio*0.05 + 0.36,
        -ratio*0.05 - 0.36,
    ])
    variable_lower_bounds = [0, 0, 0, 0, 0, 0,      0]
    variable_upper_bounds = [1, 1, 1, 1, 1, 1, np.inf]
    
    result = linprog(
        c=objective_coefficients,
        bounds=np.array((variable_lower_bounds, variable_upper_bounds)).T,
        A_ub=constraint_coefficients,
        b_ub=constraint_right_hand_side_values,
    )
    
    R1, G1, B1, R2, G2, B2, obj = result.x
    print(result)
    print()
    
    print('Confirmation:')
    print(0.2126*R1 + 0.7152*G1 + 0.0722 - (0.2126*R2 + 0.7152*G2 + 0.0722*B2), '>= 0')
    obj_lhs = result.x[:-1] @ [
        0.2126,  0.7152,  0.0722, ratio*-0.2126, ratio*-0.7152, ratio*-0.0722
    ] - (ratio*0.05 + 0.36)
    print(f'|{obj_lhs}| <= {obj}')
    print()
    
            message: Optimization terminated successfully. (HiGHS Status 7: Optimal)
            success: True
             status: 0
                fun: 0.0
                  x: [ 0.000e+00  8.918e-01  1.000e+00  0.000e+00  0.000e+00
                       0.000e+00  0.000e+00]
                nit: 1
              lower:  residual: [ 0.000e+00  8.918e-01  1.000e+00  0.000e+00
                                  0.000e+00  0.000e+00  0.000e+00]
                     marginals: [ 0.000e+00  0.000e+00  0.000e+00  0.000e+00
                                  0.000e+00  0.000e+00  1.000e+00]
              upper:  residual: [ 1.000e+00  1.082e-01  0.000e+00  1.000e+00
                                  1.000e+00  1.000e+00        inf]
                     marginals: [ 0.000e+00  0.000e+00  0.000e+00  0.000e+00
                                  0.000e+00  0.000e+00  0.000e+00]
              eqlin:  residual: []
                     marginals: []
            ineqlin:  residual: [ 7.100e-01  0.000e+00  0.000e+00]
                     marginals: [-0.000e+00 -0.000e+00 -0.000e+00]
     mip_node_count: 0
     mip_dual_bound: 0.0
            mip_gap: 0.0
    
    Confirmation:
    0.71 >= 0
    |0.0| <= 0.0
    

    In other words, it's quite possible for your objective to be "perfect" (equal to 0), which means that (using your new definition of the objective function) you can simplify the problem to

    import numpy as np
    from scipy.optimize import linprog
    
    ratio = 7
    coeffs = np.outer([-1, 1], [0.2126, 0.7152, 0.0722])
    result = linprog(
        c=np.zeros(6),
        bounds=np.array((np.zeros(6), np.ones(6))).T,
        A_ub=                 coeffs.reshape((1, -1)), b_ub=[0],
        A_eq=(([1], [ratio])*coeffs).reshape((1, -1)), b_eq=[(1 - ratio)*0.05])
    R1, G1, B1, R2, G2, B2 = result.x
    print(result)
    print()
    
    print('Confirmation:')
    print(0.2126*R1 + 0.7152*G1 + 0.0722*B1 - (0.2126*R2 + 0.7152*G2 + 0.0722*B2), '>= 0')
    obj_lhs = result.x @ [
        0.2126,  0.7152,  0.0722, ratio*-0.2126, ratio*-0.7152, ratio*-0.0722,
    ] - (ratio - 1)*0.05
    print(obj_lhs, '== 0')
    
            message: Optimization terminated successfully. (HiGHS Status 7: Optimal)
            success: True
             status: 0
                fun: 0.0
                  x: [ 1.000e+00  2.125e-02  1.000e+00  0.000e+00  0.000e+00
                       0.000e+00]
                nit: 0
              lower:  residual: [ 1.000e+00  2.125e-02  1.000e+00  0.000e+00
                                  0.000e+00  0.000e+00]
                     marginals: [ 0.000e+00  0.000e+00  0.000e+00  0.000e+00
                                  0.000e+00  0.000e+00]
              upper:  residual: [ 0.000e+00  9.787e-01  0.000e+00  1.000e+00
                                  1.000e+00  1.000e+00]
                     marginals: [ 0.000e+00  0.000e+00  0.000e+00  0.000e+00
                                  0.000e+00  0.000e+00]
              eqlin:  residual: [ 0.000e+00]
                     marginals: [-0.000e+00]
            ineqlin:  residual: [ 3.000e-01]
                     marginals: [-0.000e+00]
     mip_node_count: 0
     mip_dual_bound: 0.0
            mip_gap: 0.0
    
    Confirmation:
    0.30000000000000004 >= 0
    0.0 == 0