# Feasibility pump - algorithm implementation using the Xpress Python interface.
#
# (C) 1983-2025 Fair Isaac Corporation

import xpress as xp

def getRoundedSol(sol, I):
    rsol = sol[:]
    for i in I:
        rsol[i] = round(sol[i])
    return rsol

def computeViol(p, viol, rtype, m):
    for i in range(m):
        if rtype[i] == 'L':
            viol[i] = -viol[i]
        elif rtype[i] == 'E':
            viol[i] = abs(viol[i])
    return max(viol[:m])

p = xp.problem()
p.readProb('Data/test.lp')

n = p.attributes.cols  # Number of columns.
m = p.attributes.rows  # Number of rows.
N = range(n)

vtype = p.getColType(0, n-1)  # Obtain variable type ('C', for continuous).

I = [i for i in N if vtype[i] != 'C']  # Discrete variables.

V = p.getVariable()

p.lpOptimize()
sol = p.getSolution()
roundsol = getRoundedSol(sol, I)
slack = p.calcSlacks(roundsol)
rtype = p.getRowType(0, m - 1)

# If x_I is the vector of integer variables and x_I* is its LP value,
# the auxiliary problem is:
#
# min |x_I - x_I*|_1
# s.t. x in X
#
# where X is the original feasible set. Add new variables y and set
# their sum as the objective, then define the l1 norm with the
# constraints:
#
# y_i >= x_i - x_i*
# y_i >= - (x_i - x_i*)

y = [p.addVariable() for i in I]

p.setObjective(xp.Sum(y))  # Objective.

# RHS to be configured later.
defPenaltyPos = [y[i] >= V[I[i]] for i in range(len(I))]
defPenaltyNeg = [y[i] >= - V[I[i]] for i in range(len(I))]

p.addConstraint(defPenaltyPos, defPenaltyNeg)

slackTol = 1e-4

while computeViol(p, slack, rtype, m) > slackTol:

    # Modify definition of penalty variable.
    p.chgRHS(defPenaltyPos, [-roundsol[i] for i in I])
    p.chgRHS(defPenaltyNeg, [roundsol[i] for i in I])

    # Reoptimize.
    p.lpOptimize()
    sol = p.getSolution()

    roundsol = getRoundedSol(sol, I)
    slack = p.calcSlacks(roundsol)

# Found feasible solution.
print('Feasible solution:', roundsol[:n])
