or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-solving.mddata-structures.mdindex.mdsolvers.mdutilities.md

utilities.mddocs/

0

# Utilities and Conversions

1

2

Helper functions for matrix conversions, problem transformations, error handling, debugging support, and sample problem generation. These utilities support advanced QP solving workflows and integration with different solver backends.

3

4

## Capabilities

5

6

### Matrix and Problem Conversions

7

8

Functions for converting between different matrix formats and problem formulations to ensure compatibility with various solvers.

9

10

```python { .api }

11

def combine_linear_box_inequalities(

12

G: Optional[Union[np.ndarray, spa.csc_matrix]],

13

h: Optional[np.ndarray],

14

lb: Optional[np.ndarray],

15

ub: Optional[np.ndarray],

16

n: int

17

) -> Tuple[Union[np.ndarray, spa.csc_matrix], np.ndarray]:

18

"""

19

Combine linear inequality and box constraints into a single system.

20

21

Converts box constraints lb <= x <= ub into linear inequalities and

22

combines them with existing G x <= h constraints.

23

24

Parameters:

25

- G: Linear inequality constraint matrix

26

- h: Linear inequality constraint vector

27

- lb: Lower bound constraints (can contain -np.inf)

28

- ub: Upper bound constraints (can contain +np.inf)

29

- n: Problem dimension

30

31

Returns:

32

Tuple of (combined_G, combined_h) representing all constraints as G_new x <= h_new

33

"""

34

35

def linear_from_box_inequalities(

36

lb: Optional[np.ndarray],

37

ub: Optional[np.ndarray],

38

n: int

39

) -> Tuple[Union[np.ndarray, spa.csc_matrix], np.ndarray]:

40

"""

41

Convert box constraints to linear inequality format.

42

43

Transforms lb <= x <= ub into -I x <= -lb and I x <= ub form.

44

45

Parameters:

46

- lb: Lower bound constraints (can contain -np.inf)

47

- ub: Upper bound constraints (can contain +np.inf)

48

- n: Problem dimension

49

50

Returns:

51

Tuple of (G, h) representing box constraints as linear inequalities

52

"""

53

54

def ensure_sparse_matrices(

55

P: Union[np.ndarray, spa.csc_matrix],

56

q: np.ndarray,

57

G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,

58

h: Optional[np.ndarray] = None,

59

A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,

60

b: Optional[np.ndarray] = None,

61

lb: Optional[np.ndarray] = None,

62

ub: Optional[np.ndarray] = None,

63

) -> Tuple[

64

spa.csc_matrix,

65

np.ndarray,

66

Optional[spa.csc_matrix],

67

Optional[np.ndarray],

68

Optional[spa.csc_matrix],

69

Optional[np.ndarray],

70

Optional[np.ndarray],

71

Optional[np.ndarray],

72

]:

73

"""

74

Convert all matrices to sparse CSC format for sparse solvers.

75

76

Parameters:

77

- P, q, G, h, A, b, lb, ub: Problem matrices and vectors

78

79

Returns:

80

Tuple with all matrices converted to scipy.sparse.csc_matrix format

81

"""

82

83

def socp_from_qp(problem: Problem) -> Dict[str, Any]:

84

"""

85

Convert a quadratic program to second-order cone program format.

86

87

Transforms QP into SOCP formulation for solvers like ECOS.

88

89

Parameters:

90

- problem: Quadratic program to convert

91

92

Returns:

93

Dictionary containing SOCP formulation matrices

94

"""

95

96

def split_dual_linear_box(

97

z_combined: np.ndarray,

98

n_linear: int,

99

n_box_lower: int,

100

n_box_upper: int

101

) -> Tuple[Optional[np.ndarray], Optional[np.ndarray]]:

102

"""

103

Split combined dual multipliers back into linear and box constraint parts.

104

105

Reverses the combination performed by combine_linear_box_inequalities.

106

107

Parameters:

108

- z_combined: Combined dual multiplier vector

109

- n_linear: Number of original linear constraints

110

- n_box_lower: Number of lower bound constraints

111

- n_box_upper: Number of upper bound constraints

112

113

Returns:

114

Tuple of (z_linear, z_box) dual multipliers

115

"""

116

```

117

118

Usage example:

119

120

```python

121

import numpy as np

122

import scipy.sparse as spa

123

from qpsolvers.conversions import (

124

combine_linear_box_inequalities,

125

linear_from_box_inequalities,

126

ensure_sparse_matrices

127

)

128

129

# Convert box constraints to linear inequalities

130

n = 3

131

lb = np.array([0.0, -np.inf, 1.0])

132

ub = np.array([np.inf, 2.0, 3.0])

133

134

G_box, h_box = linear_from_box_inequalities(lb, ub, n)

135

print(f"Box constraints as linear inequalities:")

136

print(f"G_box shape: {G_box.shape}")

137

print(f"h_box: {h_box}")

138

139

# Combine with existing linear constraints

140

G_linear = np.array([[1.0, 1.0, 1.0]])

141

h_linear = np.array([5.0])

142

143

G_combined, h_combined = combine_linear_box_inequalities(

144

G_linear, h_linear, lb, ub, n

145

)

146

print(f"Combined constraints shape: {G_combined.shape}")

147

148

# Convert matrices to sparse format

149

P = np.eye(n)

150

q = np.ones(n)

151

152

P_sparse, q_out, G_sparse, h_out, A_out, b_out, lb_out, ub_out = ensure_sparse_matrices(

153

P, q, G_combined, h_combined

154

)

155

print(f"P matrix format: {type(P_sparse)}")

156

print(f"G matrix format: {type(G_sparse)}")

157

```

158

159

### Debugging and Visualization

160

161

Utilities for debugging QP problems and visualizing matrix structures.

162

163

```python { .api }

164

def print_matrix_vector(

165

A: Union[np.ndarray, spa.csc_matrix],

166

A_label: str,

167

b: np.ndarray,

168

b_label: str,

169

column_width: int = 24,

170

) -> None:

171

"""

172

Print a matrix and vector side by side for debugging.

173

174

Useful for visualizing constraint matrices and vectors together.

175

176

Parameters:

177

- A: Matrix to print (converted to dense if sparse)

178

- A_label: Label for the matrix

179

- b: Vector to print

180

- b_label: Label for the vector

181

- column_width: Width of output columns in characters

182

"""

183

```

184

185

Usage example:

186

187

```python

188

import numpy as np

189

from qpsolvers import print_matrix_vector

190

191

# Create example constraint

192

G = np.array([

193

[1.0, 2.0, -1.0],

194

[0.0, 1.0, 2.0],

195

[-1.0, 0.0, 1.0]

196

])

197

h = np.array([3.0, 2.0, 1.0])

198

199

print("Constraint visualization:")

200

print_matrix_vector(G, "G", h, "h")

201

202

# Output will show G and h matrices side by side:

203

# G = h =

204

# [[ 1. 2. -1.] [[3.]

205

# [ 0. 1. 2.] [2.]

206

# [-1. 0. 1.]] [1.]]

207

```

208

209

### Sample Problem Generation

210

211

Functions for creating test problems and benchmarks.

212

213

```python { .api }

214

def get_sparse_least_squares(n: int) -> Tuple[

215

spa.csc_matrix, # R matrix

216

np.ndarray, # s vector

217

spa.csc_matrix, # G matrix

218

np.ndarray, # h vector

219

spa.csc_matrix, # A matrix

220

np.ndarray, # b vector

221

]:

222

"""

223

Generate a sparse least squares test problem.

224

225

Creates a problem of the form:

226

minimize ||R x - s||^2

227

subject to G x <= h, A x = b

228

229

Parameters:

230

- n: Problem dimension

231

232

Returns:

233

Tuple of (R, s, G, h, A, b) defining the least squares problem

234

"""

235

236

def get_qpsut01() -> Tuple[Problem, Solution]:

237

"""

238

Get QPSUT01 problem and its solution.

239

240

Returns:

241

Tuple of (problem, solution) pair for testing

242

"""

243

244

def get_qpsut02() -> Tuple[Problem, Solution]:

245

"""

246

Get QPSUT02 problem and its solution.

247

248

Returns:

249

Tuple of (problem, solution) pair for testing

250

"""

251

252

def get_qpsut03() -> Tuple[Problem, Solution]:

253

"""

254

Get QPSUT03 problem and its solution.

255

256

This problem has partial box bounds with -infinity on some lower

257

bounds and +infinity on some upper bounds.

258

259

Returns:

260

Tuple of (problem, solution) pair for testing

261

"""

262

263

def get_qpsut04() -> Tuple[Problem, Solution]:

264

"""

265

Get QPSUT04 problem and its solution.

266

267

Returns:

268

Tuple of (problem, solution) pair for testing

269

"""

270

271

def get_qpsut05() -> Tuple[Problem, Solution]:

272

"""

273

Get QPSUT05 problem and its solution.

274

275

Returns:

276

Tuple of (problem, solution) pair for testing

277

"""

278

279

def get_qptest() -> Tuple[Problem, Solution]:

280

"""

281

Get QPTEST problem from the Maros-Meszaros test set.

282

283

Returns:

284

Tuple of (problem, solution) pair for testing

285

"""

286

287

def get_qpgurdu() -> Tuple[Problem, Solution]:

288

"""

289

Get sample random problem with linear inequality constraints.

290

291

Returns:

292

Tuple of (problem, solution) pair for testing

293

"""

294

295

def get_qpgureq() -> Tuple[Problem, Solution]:

296

"""

297

Get sample random problem with equality constraints.

298

299

Returns:

300

Tuple of (problem, solution) pair for testing

301

"""

302

303

def get_qpgurabs() -> Tuple[Problem, Solution]:

304

"""

305

Get sample random problem with box constraints.

306

307

Returns:

308

Tuple of (problem, solution) pair for testing

309

"""

310

```

311

312

Usage example:

313

314

```python

315

from qpsolvers.problems import get_sparse_least_squares, get_qpsut01

316

from qpsolvers import solve_ls, solve_problem

317

318

# Generate sparse least squares test problem

319

n = 50

320

R, s, G, h, A, b, lb, ub = get_sparse_least_squares(n)

321

322

print(f"Generated {n}-dimensional sparse least squares problem")

323

print(f"R matrix shape: {R.shape}, sparsity: {R.nnz / (R.shape[0] * R.shape[1]):.3f}")

324

print(f"G matrix shape: {G.shape}")

325

print(f"A matrix shape: {A.shape}")

326

327

# Solve the generated problem

328

x = solve_ls(R, s, G, h, A, b, lb=lb, ub=ub, solver="osqp")

329

if x is not None:

330

print(f"Solution found with residual: {np.linalg.norm(R @ x - s):.6f}")

331

332

# Use a predefined test problem

333

problem, expected_solution = get_qpsut01()

334

solution = solve_problem(problem, solver="osqp")

335

336

if solution.found:

337

print(f"QPSUT01 solved successfully")

338

print(f"Expected x: {expected_solution.x}")

339

print(f"Computed x: {solution.x}")

340

print(f"Difference: {np.linalg.norm(solution.x - expected_solution.x):.6f}")

341

```

342

343

### Exception Handling

344

345

Comprehensive exception hierarchy for error handling and debugging.

346

347

```python { .api }

348

class QPError(Exception):

349

"""

350

Base class for all qpsolvers exceptions.

351

352

All qpsolvers-specific exceptions inherit from this class to avoid

353

abstraction leakage from underlying solver libraries.

354

"""

355

356

class NoSolverSelected(QPError):

357

"""

358

Exception raised when the solver parameter is not specified.

359

360

Raised by solve_qp when no solver is specified and no default is available.

361

"""

362

363

class ParamError(QPError):

364

"""

365

Exception raised when solver parameters are incorrect.

366

367

Indicates issues with solver-specific parameter values or combinations.

368

"""

369

370

class ProblemError(QPError):

371

"""

372

Exception raised when a quadratic program is malformed.

373

374

Indicates issues with problem formulation such as:

375

- Inconsistent matrix dimensions

376

- Invalid constraint specifications

377

- Non-convex problems (when detected)

378

"""

379

380

class SolverNotFound(QPError):

381

"""

382

Exception raised when a requested solver is not available.

383

384

Includes suggestions for installing the missing solver.

385

"""

386

387

class SolverError(QPError):

388

"""

389

Exception raised when a solver fails during execution.

390

391

Wraps solver-specific errors to maintain abstraction boundaries.

392

"""

393

```

394

395

Usage example:

396

397

```python

398

from qpsolvers import solve_qp, QPError, SolverNotFound, ProblemError

399

import numpy as np

400

401

def robust_solve(P, q, G=None, h=None, A=None, b=None, solvers=None):

402

"""Robust QP solving with comprehensive error handling."""

403

404

if solvers is None:

405

from qpsolvers import available_solvers

406

solvers = available_solvers

407

408

for solver in solvers:

409

try:

410

x = solve_qp(P, q, G, h, A, b, solver=solver, verbose=False)

411

if x is not None:

412

print(f"Successfully solved with {solver}")

413

return x

414

415

except SolverNotFound:

416

print(f"Solver {solver} not available, trying next...")

417

continue

418

419

except ProblemError as e:

420

print(f"Problem is malformed: {e}")

421

return None

422

423

except QPError as e:

424

print(f"Solver {solver} failed: {e}")

425

continue

426

427

print("No solver succeeded")

428

return None

429

430

# Example usage

431

P = np.eye(3)

432

q = np.ones(3)

433

result = robust_solve(P, q, solvers=["nonexistent_solver", "osqp", "cvxopt"])

434

```

435

436

### Advanced Utilities

437

438

```python

439

def validate_qp_solution(

440

x: np.ndarray,

441

P: Union[np.ndarray, spa.csc_matrix],

442

q: np.ndarray,

443

G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,

444

h: Optional[np.ndarray] = None,

445

A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,

446

b: Optional[np.ndarray] = None,

447

lb: Optional[np.ndarray] = None,

448

ub: Optional[np.ndarray] = None,

449

tolerance: float = 1e-6

450

) -> Dict[str, Any]:

451

"""

452

Validate a QP solution against all constraints.

453

454

Parameters:

455

- x: Candidate solution

456

- P, q, G, h, A, b, lb, ub: Problem definition

457

- tolerance: Tolerance for constraint violations

458

459

Returns:

460

Dictionary with validation results and constraint violations

461

"""

462

463

results = {

464

'feasible': True,

465

'violations': {},

466

'objective': 0.5 * x.T @ P @ x + q.T @ x

467

}

468

469

# Check linear inequalities

470

if G is not None and h is not None:

471

violations = (G @ x - h).clip(min=0)

472

max_violation = np.max(violations)

473

if max_violation > tolerance:

474

results['feasible'] = False

475

results['violations']['linear_inequality'] = max_violation

476

477

# Check linear equalities

478

if A is not None and b is not None:

479

violations = np.abs(A @ x - b)

480

max_violation = np.max(violations)

481

if max_violation > tolerance:

482

results['feasible'] = False

483

results['violations']['linear_equality'] = max_violation

484

485

# Check box constraints

486

if lb is not None:

487

violations = (lb - x).clip(min=0)

488

max_violation = np.max(violations)

489

if max_violation > tolerance:

490

results['feasible'] = False

491

results['violations']['lower_bound'] = max_violation

492

493

if ub is not None:

494

violations = (x - ub).clip(min=0)

495

max_violation = np.max(violations)

496

if max_violation > tolerance:

497

results['feasible'] = False

498

results['violations']['upper_bound'] = max_violation

499

500

return results

501

502

# Example usage

503

x_candidate = np.array([0.5, 0.3, 0.2])

504

validation = validate_qp_solution(x_candidate, P, q, G, h, A, b, lb, ub)

505

print(f"Solution feasible: {validation['feasible']}")

506

print(f"Objective value: {validation['objective']:.6f}")

507

if validation['violations']:

508

print(f"Constraint violations: {validation['violations']}")

509

```