tessl install tessl/pypi-ecos@2.0.1Python interface to ECOS, a numerical solver for convex second-order cone programs.
Common errors, issues, and solutions when using ECOS.
Cause: The inequality constraint matrix G is not a scipy sparse matrix.
Solution: Convert to scipy.sparse.csc_matrix:
import scipy.sparse as sp
# WRONG: Dense numpy array
G = np.array([[1.0, 2.0], [3.0, 4.0]])
# CORRECT: CSC sparse matrix
G = sp.csc_matrix([[1.0, 2.0], [3.0, 4.0]])Prevention: Always use sp.csc_matrix() when creating constraint matrices.
Cause: The equality constraint matrix A is not a scipy sparse matrix.
Solution: Convert to scipy.sparse.csc_matrix:
import scipy.sparse as sp
# WRONG: Dense numpy array
A = np.array([[1.0, 2.0]])
# CORRECT: CSC sparse matrix
A = sp.csc_matrix([[1.0, 2.0]])Cause: Provided only A without b, or only b without A.
Solution: Provide both or neither:
# WRONG: Only A provided
solution = ecos.solve(c, G, h, dims, A=A)
# CORRECT: Both provided
solution = ecos.solve(c, G, h, dims, A=A, b=b)
# CORRECT: Neither provided (no equality constraints)
solution = ecos.solve(c, G, h, dims)Cause: A and G have different numbers of columns (different variable counts).
Solution: Ensure both matrices have the same number of columns:
n = 3 # Number of variables
# WRONG: Different column counts
G = sp.csc_matrix(np.zeros((5, 3))) # 3 columns
A = sp.csc_matrix(np.zeros((2, 4))) # 4 columns - MISMATCH
# CORRECT: Same column count
G = sp.csc_matrix(np.zeros((5, n))) # n columns
A = sp.csc_matrix(np.zeros((2, n))) # n columns
c = np.zeros(n) # Must also matchDebugging:
print("Variables in c:", len(c))
print("Columns in G:", G.shape[1])
print("Columns in A:", A.shape[1] if A is not None else "N/A")
# All should be equalCause: Keyword argument verbose has wrong type.
Solution: Use boolean values:
# WRONG: String
solution = ecos.solve(c, G, h, dims, verbose="true")
# WRONG: Integer
solution = ecos.solve(c, G, h, dims, verbose=1)
# CORRECT: Boolean
solution = ecos.solve(c, G, h, dims, verbose=True)
solution = ecos.solve(c, G, h, dims, verbose=False)Cause: Keyword argument max_iters is float instead of int.
Solution: Use integer values:
# WRONG: Float
solution = ecos.solve(c, G, h, dims, max_iters=100.0)
# CORRECT: Integer
solution = ecos.solve(c, G, h, dims, max_iters=100)Prevention: All iteration-related parameters must be integers:
max_itersnitrefmi_max_itersCause: The linear constraint dimension is negative or not an integer.
Solution: Use non-negative integer:
# WRONG: Negative
dims = {'l': -1, 'q': []}
# WRONG: Float
dims = {'l': 2.0, 'q': []}
# CORRECT: Non-negative integer
dims = {'l': 2, 'q': []}
dims = {'l': 0, 'q': [3]} # Zero is validCause: The second-order cone dimensions are specified as integer instead of list.
Solution: Always use a list (even for single cone):
# WRONG: Integer instead of list
dims = {'l': 0, 'q': 3}
# CORRECT: List with one element
dims = {'l': 0, 'q': [3]}
# CORRECT: Empty list (no SOCs)
dims = {'l': 5, 'q': []}
# CORRECT: Multiple cones
dims = {'l': 0, 'q': [3, 4, 2]}Cause: Provided negative or zero tolerance parameter.
Solution: All tolerances must be positive floats:
# WRONG: Negative tolerance
solution = ecos.solve(c, G, h, dims, feastol=-1e-8)
# WRONG: Zero tolerance
solution = ecos.solve(c, G, h, dims, abstol=0.0)
# CORRECT: Positive tolerance
solution = ecos.solve(c, G, h, dims, feastol=1e-8, abstol=1e-8, reltol=1e-8)Affected parameters:
feastolabstolreltolfeastol_inaccabstol_inaccreltol_inaccmi_abs_epsmi_rel_epsmi_int_tolCause: Provided negative iteration count.
Solution: Use non-negative integers:
# WRONG: Negative
solution = ecos.solve(c, G, h, dims, max_iters=-10)
# CORRECT: Non-negative
solution = ecos.solve(c, G, h, dims, max_iters=100)
solution = ecos.solve(c, G, h, dims, max_iters=0) # Zero is valid (no iterations)Cause: Second-order cone dimension is not a positive integer.
Solution: All SOC dimensions must be >= 1:
# WRONG: Zero dimension
dims = {'l': 0, 'q': [0]}
# WRONG: Negative dimension
dims = {'l': 0, 'q': [3, -2]}
# CORRECT: Positive integers only
dims = {'l': 0, 'q': [3, 4, 2]}Note: The minimum second-order cone dimension is 1, though typically cones have dimension >= 2 for meaningful constraints.
Cause: Total dimensions in dims don't sum to the number of rows in G.
Solution: Ensure dims['l'] + sum(dims['q']) + 3*dims['e'] equals G.shape[0]:
# Example: G has 10 rows
G = sp.csc_matrix(np.zeros((10, 3)))
h = np.zeros(10)
# WRONG: Only accounts for 5 rows
dims = {'l': 5, 'q': []} # 5 ≠ 10
# WRONG: Accounts for 13 rows
dims = {'l': 5, 'q': [3, 5]} # 5 + 3 + 5 = 13 ≠ 10
# CORRECT: Exactly 10 rows
dims = {'l': 4, 'q': [3, 3]} # 4 + 3 + 3 = 10 ✓
dims = {'l': 10, 'q': []} # 10 + 0 = 10 ✓
dims = {'l': 0, 'q': [10]} # 0 + 10 = 10 ✓Debugging:
m = G.shape[0]
dims_total = dims['l'] + sum(dims['q']) + 3 * dims.get('e', 0)
print(f"G rows: {m}, dims total: {dims_total}")
assert m == dims_total, f"Mismatch: {m} != {dims_total}"Cause: Length of h doesn't match number of rows in G.
Solution: Ensure len(h) == G.shape[0]:
# WRONG: Dimension mismatch
G = sp.csc_matrix(np.zeros((5, 3))) # 5 rows
h = np.zeros(3) # Only 3 elements
# CORRECT: Matching dimensions
G = sp.csc_matrix(np.zeros((5, 3))) # 5 rows
h = np.zeros(5) # 5 elementsCause: Length of b doesn't match number of rows in A.
Solution: Ensure len(b) == A.shape[0]:
# WRONG: Dimension mismatch
A = sp.csc_matrix(np.zeros((3, 5))) # 3 rows
b = np.zeros(5) # Wrong dimension
# CORRECT: Matching dimensions
A = sp.csc_matrix(np.zeros((3, 5))) # 3 rows
b = np.zeros(3) # 3 elementsSymptoms:
solution['info']['iter'] equals max_itersCauses:
Solutions:
# Solution 1: Increase iteration limit
solution = ecos.solve(c, G, h, dims, max_iters=200) # Default is typically 100
# Solution 2: Relax tolerances
solution = ecos.solve(
c, G, h, dims,
feastol=1e-6, # Looser than default 1e-8
abstol=1e-6,
reltol=1e-6
)
# Solution 3: Use iterative refinement
solution = ecos.solve(c, G, h, dims, nitref=20)
# Solution 4: Check problem scaling
print("c range:", c.min(), "to", c.max())
print("G range:", G.data.min(), "to", G.data.max())
print("h range:", h.min(), "to", h.max())
# If ranges differ by many orders of magnitude, consider scalingScaling example:
# Scale variables and constraints
c_scale = np.max(np.abs(c))
c_scaled = c / c_scale
G_scale = np.max(np.abs(G.data))
G_scaled = G / G_scale
h_scale = np.max(np.abs(h))
h_scaled = h / h_scale
solution = ecos.solve(c_scaled, G_scaled, h_scaled, dims)
# Remember to unscale solution
x_original = solution['x'] # Already in original scale if only c was scaledSymptoms:
solution['info'] shows infeasibility statusCauses:
Debugging:
# Check if constraints can all be satisfied
# Start with equality constraints
if A is not None:
print("Equality constraints: A*x = b")
print("A shape:", A.shape)
print("b:", b)
# Check if system is consistent
from scipy.linalg import lstsq
x_eq, residual, rank, s = lstsq(A.toarray(), b)
print("Least squares residual:", residual)
if np.any(residual > 1e-10):
print("WARNING: Equality constraints may be inconsistent")
# Check individual inequality constraints
print("\nInequality constraint analysis:")
for i in range(min(5, G.shape[0])): # Check first few
print(f"Constraint {i}: G[{i},:]*x <= {h[i]}")Solutions:
# Solution 1: Relax tight constraints
h_relaxed = h + 1e-6 # Add small margin
# Solution 2: Check for redundant or conflicting constraints
# Remove or modify conflicting constraints
# Solution 3: Use inaccurate tolerances (solver tries harder)
solution = ecos.solve(
c, G, h, dims,
feastol_inacc=1e-3,
abstol_inacc=1e-4,
reltol_inacc=1e-4
)Symptoms:
solution['info']Debugging:
solution = ecos.solve(c, G, h, dims, A, b)
x = solution['x']
# Check constraint violations
if A is not None:
eq_violation = np.linalg.norm(A @ x - b)
print("Equality constraint violation:", eq_violation)
ineq_slack = h - G @ x
ineq_violation = np.sum(np.minimum(ineq_slack, 0)) # Negative = violation
print("Inequality constraint violation:", ineq_violation)
# Check optimality
print("Objective value:", c @ x)
print("Solver info:", solution['info'])Solutions:
# Solution 1: Tighten tolerances
solution = ecos.solve(
c, G, h, dims,
feastol=1e-9,
abstol=1e-9,
reltol=1e-9,
max_iters=200
)
# Solution 2: More iterative refinement
solution = ecos.solve(c, G, h, dims, nitref=20)
# Solution 3: Check problem conditioning
# Ill-conditioned problems may need rescalingCauses:
Solutions:
# Solution 1: Relax gap tolerances
solution = ecos.solve(
c, G, h, dims,
bool_vars_idx=bool_idx,
mi_abs_eps=1e-3, # Looser than default
mi_rel_eps=1e-2,
mi_max_iters=500
)
# Solution 2: Provide good initial bound (if known)
# Solve continuous relaxation first
relaxed = ecos.solve(c, G, h, dims)
print("Relaxed objective:", c @ relaxed['x'])
# Use this information to guide expectations
# Solution 3: Limit branch-and-bound iterations
solution = ecos.solve(
c, G, h, dims,
bool_vars_idx=bool_idx,
mi_max_iters=100, # Stop early if needed
mi_verbose=True # Monitor progress
)Symptoms:
Debugging:
solution = ecos.solve(
c, G, h, dims,
bool_vars_idx=bool_idx,
mi_verbose=True
)
x = solution['x']
for idx in bool_idx:
print(f"x[{idx}] = {x[idx]}")
if not (np.isclose(x[idx], 0.0) or np.isclose(x[idx], 1.0)):
print(f" WARNING: Not exactly 0 or 1")Solutions:
# Solution 1: Tighten integer tolerance
solution = ecos.solve(
c, G, h, dims,
bool_vars_idx=bool_idx,
mi_int_tol=1e-6 # Default is typically 1e-4
)
# Solution 2: Round solution manually (use with caution)
x = solution['x']
for idx in bool_idx:
x[idx] = np.round(x[idx])
# Verify constraints are still satisfied
if A is not None:
print("Equality violation after rounding:", np.linalg.norm(A @ x - b))
print("Inequality violation after rounding:", np.sum(np.minimum(h - G @ x, 0)))
# Solution 3: Allow more branch-and-bound iterations
solution = ecos.solve(
c, G, h, dims,
bool_vars_idx=bool_idx,
mi_max_iters=2000
)Causes:
Solutions:
# Solution 1: Ensure matrices are sparse
print("G sparsity:", G.nnz / (G.shape[0] * G.shape[1]))
# Should be much less than 1.0 for large problems
# Solution 2: Reduce iterative refinement
solution = ecos.solve(c, G, h, dims, nitref=5) # Default is ~9
# Solution 3: Loosen tolerances
solution = ecos.solve(
c, G, h, dims,
feastol=1e-6, # Less accurate but faster
abstol=1e-6,
reltol=1e-6
)
# Solution 4: Consider if ECOS is the right solver
# For very large problems (>100,000 variables), consider:
# - SCS (larger scale, less accurate)
# - Mosek or Gurobi (commercial, very fast)Causes:
Solutions:
# Solution 1: Verify sparse format
print("G is sparse:", sp.isspmatrix(G))
print("G memory (bytes):", G.data.nbytes + G.indices.nbytes + G.indptr.nbytes)
# If you accidentally created dense:
G_dense = G.toarray() # DON'T DO THIS for large matrices
# Instead keep in sparse format
# Solution 2: Use CSC format (required by ECOS)
# CSC is optimal for column-wise operations
# Solution 3: For extremely large problems, consider problem decomposition
# Break problem into smaller subproblems if possibleCause: G was provided in a sparse format other than CSC (e.g., CSR, COO).
Impact: Automatic conversion happens but adds overhead.
Solution: Create matrix as CSC from the start:
# CAUSES WARNING: CSR format
G = sp.csr_matrix([[1.0, 2.0], [3.0, 4.0]])
solution = ecos.solve(c, G, h, dims) # Warning printed
# PREFERRED: CSC format
G = sp.csc_matrix([[1.0, 2.0], [3.0, 4.0]])
solution = ecos.solve(c, G, h, dims) # No warning# WRONG
dims = {'l': 0, 'q': 3} # Integer, not list
# CORRECT
dims = {'l': 0, 'q': [3]} # List with one element# Setup
n = 3
c = np.array([1.0, 2.0, 3.0]) # 3 variables
# WRONG: G has wrong number of columns
G = sp.csc_matrix([[1.0, 2.0]]) # Only 2 columns
# CORRECT: Consistent dimensions
G = sp.csc_matrix([[1.0, 2.0, 3.0]]) # 3 columns matching c# Second-order cone: (t, x) where t >= ||x||
# First row is the BOUND, remaining rows are the VECTOR
# WRONG interpretation: want x[0] >= ||(x[1], x[2])||
dims = {'l': 0, 'q': [3]}
# Rows 0, 1, 2 of G correspond to (t, x1, x2) of cone
# So G[0,:]*x is the bound, G[1,:]*x and G[2,:]*x are the vector components
# CORRECT setup:
# If variables are [x0, x1, x2] and we want x0 >= ||(x1, x2)||:
G = sp.csc_matrix([
[1.0, 0.0, 0.0], # Row 0: x0 (bound)
[0.0, 1.0, 0.0], # Row 1: x1 (vector component 1)
[0.0, 0.0, 1.0] # Row 2: x2 (vector component 2)
])
h = np.zeros(3)
dims = {'l': 0, 'q': [3]}# BAD: Blindly using solution
solution = ecos.solve(c, G, h, dims)
x = solution['x'] # May be infeasible or inaccurate!
# GOOD: Check solver status first
solution = ecos.solve(c, G, h, dims)
info = solution['info']
exit_flag = info.get('exitFlag', None)
if exit_flag == 0:
print("Optimal solution found")
x = solution['x']
elif exit_flag == 1:
print("Inaccurate solution (might be acceptable)")
x = solution['x']
else:
print("Solver did not converge properly")
print("Info:", info)
# Handle error caseWhen encountering issues, check:
Matrix formats:
print("G is CSC:", sp.isspmatrix_csc(G))
print("A is CSC:", sp.isspmatrix_csc(A) if A is not None else "N/A")Dimension consistency:
n = len(c)
m = G.shape[0]
p = A.shape[0] if A is not None else 0
print(f"Variables: {n}")
print(f"Inequality constraints: {m}")
print(f"Equality constraints: {p}")
print(f"G shape: {G.shape}, expected ({m}, {n})")
if A is not None:
print(f"A shape: {A.shape}, expected ({p}, {n})")
print(f"h shape: {h.shape}, expected ({m},)")
if b is not None:
print(f"b shape: {b.shape}, expected ({p},)")Cone dimensions:
dims_total = dims['l'] + sum(dims['q']) + 3 * dims.get('e', 0)
print(f"Total cone dimension: {dims_total}")
print(f"G rows: {G.shape[0]}")
print(f"Match: {dims_total == G.shape[0]}")Data types:
print(f"c dtype: {c.dtype}")
print(f"h dtype: {h.dtype}")
print(f"G dtype: {G.dtype}")Solution quality:
solution = ecos.solve(c, G, h, dims, A, b)
x = solution['x']
print("Objective value:", c @ x)
if A is not None:
print("Equality residual:", np.linalg.norm(A @ x - b))
print("Inequality slack range:", (h - G @ x).min(), "to", (h - G @ x).max())
print("Exit flag:", solution['info'].get('exitFlag'))
print("Iterations:", solution['info'].get('iter'))