Skip to content

Commit a0829fa

Browse files
committed
Added setup.py file. Added usage description README
1 parent 32f2edf commit a0829fa

13 files changed

+169
-113
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,6 @@ ENV/
9999

100100
# Backups
101101
results_bak/
102+
103+
# Vim backup files
104+
*.swp

README.md

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,60 @@
1-
# MIQP python solver based on OSQP
2-
The solver lives in the `miosqp` package. In order to run it you need to install the [OSQP Python interface](https://github.com/bstellato/osqp).
1+
# Mixed-Integer Quadratic Program Python Solver Based on OSQP
32

4-
## Run example
3+
miOSQP solves an mixed-integer quadratic programs (MIQPs) of the form
4+
```
5+
minimize 0.5 x' P x + q' x
6+
7+
subject to l <= A x <= u
8+
x[i] in Z for i in i_idx
9+
i_l[i] <= x[i] <= i_u[i] for i in i_idx
10+
```
11+
where `i_idx` is a vector of indices of which variables are integer and `i_l`, `i_u` are the lower and upper bounds on the integer variables respectively.
12+
13+
14+
## Installation
15+
To install the package simply run
16+
```
17+
python setup.py install
18+
```
19+
it depends on [OSQP](osqp.readthedocs.io), numpy and scipy.
20+
21+
22+
## Usage
23+
To solve a MIQP we need to run
24+
```python
25+
import miosqp
26+
m = miosqp.MIOSQP()
27+
m.setup(P, q, A, l, u, i_idx, i_l, i_u)
28+
results = m.solve()
29+
```
30+
where `P` is a symmetric positive semidefinite matrix and `A` a matrix.
31+
`P` and `A` are both in the scipy sparse CSC format.
32+
33+
The returned object `results` contains
34+
- `x`: the solution
35+
- `upper_glob`: the cost function upper bound
36+
- `run_time`: the solution time
37+
- `status`: the status
38+
- `osqp_solve_time`: the OSQP solve time as a percentage of the total solution time
39+
- `osqp_iter_avg`: the OSQP average number of iterations for each QP sub-problem solution
40+
41+
42+
### Update problem vectors
43+
Problem vectors can be updated without running the setup again. It can be done with
44+
```python
45+
m.update_vectors(q=q_new, l=l_new, u=u_new)
46+
```
47+
48+
### Set initial solution guess
49+
The initial guess can speedup the branch-and-bound algorithm significantly.
50+
To set an initial feasible solution `x0` we can run
51+
```python
52+
m.set_x0(x0)
53+
```
54+
55+
56+
57+
## Run examples
558
In order to run the examples from to compare with GUROBI, after installing the python insterface, you need to install [mathprogbasepy](https://github.com/bstellato/mathprogbasepy). Examples can be found in the `examples` folder.
659

760
- Random MIQPs
@@ -10,5 +63,5 @@ In order to run the examples from to compare with GUROBI, after installing the p
1063
Note that you need [pandas](http://pandas.pydata.org/) package for storing the results dataframe and [tqdm](https://github.com/noamraph/tqdm) package for the progress bar.
1164

1265

13-
## Maximum number of iterations
14-
For some problem instances, OSQP reaches the maximum number of iterations. In order to deal with them, they are dumped to different files in the `max_iter_examples` folder. In order to load them separately and solve them with OSQP, you can run `examples/run_maxiter_problem.py` file.
66+
<!-- ## Maximum number of iterations -->
67+
<!-- For some problem instances, OSQP reaches the maximum number of iterations. In order to deal with them, they are dumped to different files in the `max_iter_examples` folder. In order to load them separately and solve them with OSQP, you can run `examples/run_maxiter_problem.py` file. -->

examples/power_converter/power_converter.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ def compute_mpc_input(self, x0, u_prev, solver='gurobi'):
462462
'eps_rel': 1e-03,
463463
'eps_prim_inf': 1e-04,
464464
# 'rho': 0.001,
465-
'rho': 0.1,
465+
# 'rho': 0.1,
466466
'verbose': False}
467467
self.solver = miosqp.MIOSQP()
468468
self.solver.setup(qp.P, q, qp.A, qp.l,
@@ -490,21 +490,22 @@ def compute_mpc_input(self, x0, u_prev, solver='gurobi'):
490490
# import ipdb; ipdb.set_trace()
491491

492492

493-
494493
if res_miosqp.status != miosqp.MI_SOLVED:
495494
import ipdb; ipdb.set_trace()
496495
u = res_miosqp.x
497496
obj_val = res_miosqp.upper_glob
498497
solve_time = res_miosqp.run_time
499-
498+
osqp_solve_time = 100 * res_miosqp.osqp_solve_time / res_miosqp.run_time
500499

501500
# Get first input
502501
u0 = u[:6]
503502

504503
if solver == 'miosqp':
505-
return u0, obj_val, solve_time, u, res_miosqp.osqp_iter_avg
504+
return u0, obj_val, solve_time, u, \
505+
osqp_solve_time, \
506+
res_miosqp.osqp_iter_avg
506507
else:
507-
return u0, obj_val, solve_time, u, 0
508+
return u0, obj_val, solve_time, u, 0, 0
508509

509510
def simulate_one_step(self, x, u):
510511
"""
@@ -515,7 +516,6 @@ def simulate_one_step(self, x, u):
515516

516517
return xnew, ynew
517518

518-
519519
def compute_signals(self, X):
520520
"""
521521
Compute signals for plotting
@@ -533,7 +533,6 @@ def compute_signals(self, X):
533533
for i in range(T_final):
534534
Y_star_phase[:, i] = self.params.invP.dot(X[4:6, i])
535535

536-
537536
# Compute torque
538537
T_e = np.zeros(T_final)
539538
for i in range(T_final):
@@ -601,6 +600,7 @@ def simulate_cl(self, N, steady_trans, solver='gurobi', plot=False):
601600
if solver == 'miosqp':
602601
# If miosqp, set avg numer of iterations to 0
603602
miosqp_avg_osqp_iter = 0
603+
miosqp_osqp_avg_time = 0
604604

605605
# Rename some variables for notation ease
606606
nx = self.dyn_system.A.shape[0]
@@ -629,7 +629,7 @@ def simulate_cl(self, N, steady_trans, solver='gurobi', plot=False):
629629
for i in tqdm(range(T_final)):
630630

631631
# Compute mpc inputs
632-
U[:, i], obj_vals[i], time_temp, u_prev, osqp_iter = \
632+
U[:, i], obj_vals[i], time_temp, u_prev, osqp_time, osqp_iter = \
633633
self.compute_mpc_input(X[:, i], u_prev, solver=solver)
634634

635635
# Store time if after the init periods
@@ -646,10 +646,13 @@ def simulate_cl(self, N, steady_trans, solver='gurobi', plot=False):
646646
if solver == 'miosqp':
647647
# Append average number of osqp iterations
648648
miosqp_avg_osqp_iter += osqp_iter
649+
miosqp_osqp_avg_time += osqp_time
649650

650651
if solver == 'miosqp':
651-
# Divide total number of average iterations by time steps
652+
# Divide total number of average OSQP iterations
653+
# and solve time by time steps
652654
miosqp_avg_osqp_iter /= T_final
655+
miosqp_osqp_avg_time /= T_final
653656

654657
# Compute additional signals for plotting
655658
Y_phase, Y_star_phase, T_e, T_e_des = self.compute_signals(X)
@@ -667,5 +670,6 @@ def simulate_cl(self, N, steady_trans, solver='gurobi', plot=False):
667670

668671
if solver == 'miosqp':
669672
stats.miosqp_avg_osqp_iter = miosqp_avg_osqp_iter
673+
stats.miosqp_osqp_avg_time = miosqp_osqp_avg_time
670674

671675
return stats

examples/power_converter/run_example.py

Lines changed: 19 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -86,73 +86,60 @@ def run_example():
8686
miosqp_min_time = np.zeros(len(N_adp))
8787
miosqp_max_time = np.zeros(len(N_adp))
8888
miosqp_avg_osqp_iter = np.zeros(len(N_adp))
89-
89+
miosqp_osqp_avg_time = np.zeros(len(N_adp))
9090

9191
# Simulate model
92-
9392
for i in range(len(N_adp)):
9493

95-
stats_gurobi = model.simulate_cl(N_adp[i], flag_steady_trans, solver='gurobi')
94+
stats_gurobi = model.simulate_cl(N_adp[i], flag_steady_trans,
95+
solver='gurobi')
9696

9797
gurobi_std_time[i] = stats_gurobi.std_solve_time
9898
gurobi_avg_time[i] = stats_gurobi.avg_solve_time
9999
gurobi_min_time[i] = stats_gurobi.min_solve_time
100100
gurobi_max_time[i] = stats_gurobi.max_solve_time
101101

102-
103102
# Make plots for horizon 3
104103
if N_adp[i] == 3:
105104
plot_flag = 1
106105
else:
107106
plot_flag = 0
108107

109-
stats_miosqp = model.simulate_cl(N_adp[i], flag_steady_trans, solver='miosqp', plot=plot_flag)
110-
108+
stats_miosqp = model.simulate_cl(N_adp[i], flag_steady_trans,
109+
solver='miosqp', plot=plot_flag)
111110

112111
miosqp_std_time[i] = stats_miosqp.std_solve_time
113112
miosqp_avg_time[i] = stats_miosqp.avg_solve_time
114113
miosqp_min_time[i] = stats_miosqp.min_solve_time
115114
miosqp_max_time[i] = stats_miosqp.max_solve_time
116115
miosqp_avg_osqp_iter[i] = stats_miosqp.miosqp_avg_osqp_iter
117-
118-
116+
miosqp_osqp_avg_time[i] = stats_miosqp.miosqp_osqp_avg_time
119117

120118
# Create pandas dataframe
121-
timings = pd.DataFrame({ 'grb_avg' : gurobi_avg_time,
122-
'grb_std' : gurobi_std_time,
123-
'grb_min' : gurobi_min_time,
124-
'grb_max' : gurobi_max_time,
125-
'miosqp_avg' : miosqp_avg_time,
126-
'miosqp_std' : miosqp_std_time,
127-
'miosqp_min' : miosqp_min_time,
128-
'miosqp_max' : miosqp_max_time,
129-
'miosqp_avg_osqp_iter': miosqp_avg_osqp_iter},
130-
index=N_adp)
131-
132-
133-
119+
timings = pd.DataFrame({'T': N_adp,
120+
'grb_avg': gurobi_avg_time,
121+
'grb_std': gurobi_std_time,
122+
'grb_min': gurobi_min_time,
123+
'grb_max': gurobi_max_time,
124+
'miosqp_avg': miosqp_avg_time,
125+
'miosqp_std': miosqp_std_time,
126+
'miosqp_min': miosqp_min_time,
127+
'miosqp_max': miosqp_max_time,
128+
'miosqp_osqp_avg_time': miosqp_osqp_avg_time,
129+
'miosqp_avg_osqp_iter': miosqp_avg_osqp_iter})
134130

135131
print("Results")
136132
print(timings)
137133

138134
timings.to_csv('results/power_converter_timings.csv')
139135

140-
# # Converting results to latex table and storing them to a file
141-
# formatter = lambda x: '%1.2f' % x
142-
# latex_table = timings.to_latex(header=False,
143-
# float_format=formatter)
144-
# f = open('results/power_converter_miqp.tex', 'w')
145-
# f.write(latex_table)
146-
# f.close()
147-
148-
149136
# Create error plots with fill_between
150137
plt.figure()
151138
ax = plt.gca()
152139
plt.semilogy(N_adp, gurobi_avg_time, color=colors['o'],
153-
label='GUROBI')
140+
label='GUROBI')
154141
plt.semilogy(N_adp, miosqp_avg_time, color=colors['b'],
155-
label='miOSQP')
142+
label='miOSQP')
156143
plt.xticks(N_adp)
157144
ax.set_xlabel(r'Horizon length $T$')
158145
ax.set_ylabel(r'Time [s]')
@@ -162,8 +149,3 @@ def run_example():
162149
# plt.show()
163150
plt.savefig('results/power_converter_timings.pdf')
164151

165-
166-
167-
168-
169-
#

0 commit comments

Comments
 (0)