import numpy as np
from scipy.optimize import LinearConstraint
def combine_multiple_linear_constraints(constraints):
full_A = constraints[0].A
full_lb = constraints[0].lb
full_ub = constraints[0].ub
for constraint in constraints[1:]:
full_A = np.concatenate((full_A, constraint.A), axis=0)
full_lb = np.concatenate((full_lb, constraint.lb), axis=0)
full_ub = np.concatenate((full_ub, constraint.ub), axis=0)
return LinearConstraint(full_A, full_lb, full_ub)
def separate_LC_into_eq_and_ineq(linear_constraint):
# The Python interface receives linear constraints lb <= A*x <= ub, but the
# Fortran backend of PRIMA expects that the linear constraints are specified
# as A_eq*x = b_eq, A_ineq*x <= b_ineq.
# As such, we must:
# 1. for constraints with lb == ub, rewrite them as A_eq*x = lb;
# 2. for constraints with lb < ub, rewrite them as A_ineq*x <= b_ineq.
# We suppose lb == ub if ub <= lb + 2*epsilon, assuming that the preprocessing
# ensures lb <= ub.
epsilon = np.finfo(np.float64).eps
eq_indices = (linear_constraint.ub <= (linear_constraint.lb + 2*epsilon))
A_eq = linear_constraint.A[eq_indices]
b_eq = (linear_constraint.lb[eq_indices] + linear_constraint.ub[eq_indices])/2.0
ineq_lb_indices = (linear_constraint.lb > -np.inf)
A_ineq_lb = -linear_constraint.A[~eq_indices & ineq_lb_indices]
b_ineq_lb = -linear_constraint.lb[~eq_indices & ineq_lb_indices]
ineq_ub_indices = (linear_constraint.ub < np.inf)
A_ineq_ub = linear_constraint.A[~eq_indices & ineq_ub_indices]
b_ineq_ub = linear_constraint.ub[~eq_indices & ineq_ub_indices]
A_ineq = np.concatenate((A_ineq_lb, A_ineq_ub))
b_ineq = np.concatenate((b_ineq_lb, b_ineq_ub))
# Ensure dtype is float64, or set to None if empty
A_eq = np.array(A_eq, dtype=np.float64) if len(A_eq) > 0 else None
b_eq = np.array(b_eq, dtype=np.float64) if len(b_eq) > 0 else None
A_ineq = np.array(A_ineq, dtype=np.float64) if len(A_ineq) > 0 else None
b_ineq = np.array(b_ineq, dtype=np.float64) if len(b_ineq) > 0 else None
return A_eq, b_eq, A_ineq, b_ineq